Skip to content
Merged
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
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ dependencies {
// gson
implementation 'com.google.code.gson:gson:2.8.6'

// base64
implementation 'commons-codec:commons-codec:1.15'

compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'
Expand Down
9 changes: 7 additions & 2 deletions src/docs/asciidoc/posts.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ operation::post-controller-test/create-post[snippets='http-request,curl-request,

operation::post-controller-test/find-post[snippets='http-request,curl-request,path-parameters,http-response,response-fields']

[[개사굴-공유-url-조회]]
=== `GET` 게시글 공유 url 조회

operation::post-controller-test/find-post_share-url[snippets='http-request,curl-request,path-parameters,http-response,response-fields']

[[게시글-목록-조회]]

[[사진-투표-현황-조회]]
=== `GET` 사진 투표 현황 조회

Expand All @@ -32,8 +39,6 @@ operation::post-controller-test/find-voted-post[snippets='http-request,curl-requ

operation::post-controller-test/close-post[snippets='http-request,curl-request,path-parameters,request-headers,http-response']

[[게시글-수정]]

[[게시글-삭제]]
=== `DELETE` 게시글 삭제

Expand Down
15 changes: 14 additions & 1 deletion src/main/java/com/swyp8team2/auth/application/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
import com.swyp8team2.auth.domain.Provider;
import com.swyp8team2.auth.domain.SocialAccount;
import com.swyp8team2.auth.domain.SocialAccountRepository;
import com.swyp8team2.common.annotation.GuestTokenCryptoService;
import com.swyp8team2.crypto.application.CryptoService;
import com.swyp8team2.user.application.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class AuthService {

private final JwtService jwtService;
Expand All @@ -23,6 +23,19 @@ public class AuthService {
private final UserService userService;
private final CryptoService cryptoService;

public AuthService(
JwtService jwtService,
OAuthService oAuthService,
SocialAccountRepository socialAccountRepository,
UserService userService,
@GuestTokenCryptoService CryptoService cryptoService) {
this.jwtService = jwtService;
this.oAuthService = oAuthService;
this.socialAccountRepository = socialAccountRepository;
this.userService = userService;
this.cryptoService = cryptoService;
}

@Transactional
public TokenPair oauthSignIn(String code, String redirectUri) {
OAuthUserInfo oAuthUserInfo = oAuthService.getUserInfo(code, redirectUri);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.swyp8team2.auth.presentation.filter;

import com.swyp8team2.auth.domain.UserInfo;
import com.swyp8team2.common.annotation.GuestTokenCryptoService;
import com.swyp8team2.common.exception.ApplicationException;
import com.swyp8team2.common.exception.BadRequestException;
import com.swyp8team2.common.exception.ErrorCode;
Expand All @@ -27,11 +28,14 @@
import static com.swyp8team2.auth.presentation.filter.JwtAuthenticationEntryPoint.EXCEPTION_KEY;

@Slf4j
@RequiredArgsConstructor
public class GuestAuthFilter extends OncePerRequestFilter {

private final CryptoService cryptoService;

public GuestAuthFilter(@GuestTokenCryptoService CryptoService cryptoService) {
this.cryptoService = cryptoService;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.swyp8team2.common.annotation;

import org.springframework.beans.factory.annotation.Qualifier;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Qualifier(GuestTokenCryptoService.QUALIFIER)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER, ElementType.METHOD})
public @interface GuestTokenCryptoService {

String QUALIFIER = "guestTokenCryptoService";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.swyp8team2.common.annotation;

import org.springframework.beans.factory.annotation.Qualifier;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Qualifier(ShareUrlCryptoService.QUALIFIER)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER, ElementType.METHOD})
public @interface ShareUrlCryptoService {

String QUALIFIER = "shareUrlCryptoService";
}
23 changes: 23 additions & 0 deletions src/main/java/com/swyp8team2/common/config/CryptoConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.swyp8team2.common.config;

import com.swyp8team2.common.annotation.GuestTokenCryptoService;
import com.swyp8team2.crypto.application.CryptoService;
import com.swyp8team2.common.annotation.ShareUrlCryptoService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CryptoConfig {

@GuestTokenCryptoService
@Bean(name = GuestTokenCryptoService.QUALIFIER)
public CryptoService guestTokenCryptoService() throws Exception {
return new CryptoService();
}

@ShareUrlCryptoService
@Bean(name = ShareUrlCryptoService.QUALIFIER)
public CryptoService shareUrlCryptoService() throws Exception {
return new CryptoService();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.swyp8team2.auth.presentation.filter.HeaderTokenExtractor;
import com.swyp8team2.auth.presentation.filter.JwtAuthFilter;
import com.swyp8team2.auth.presentation.filter.JwtAuthenticationEntryPoint;
import com.swyp8team2.common.annotation.GuestTokenCryptoService;
import com.swyp8team2.crypto.application.CryptoService;
import com.swyp8team2.user.domain.Role;
import org.springframework.beans.factory.annotation.Qualifier;
Expand All @@ -23,13 +24,10 @@
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;

import java.util.List;

@Configuration
@EnableWebSecurity
public class SecurityConfig {
Expand All @@ -39,7 +37,7 @@ public class SecurityConfig {

public SecurityConfig(
@Qualifier("handlerExceptionResolver") HandlerExceptionResolver handlerExceptionResolver,
CryptoService cryptoService
@GuestTokenCryptoService CryptoService cryptoService
) {
this.handlerExceptionResolver = handlerExceptionResolver;
this.cryptoService = cryptoService;
Expand Down Expand Up @@ -112,7 +110,8 @@ public static MvcRequestMatcher[] getWhiteList(HandlerMappingIntrospector intros
return new MvcRequestMatcher[]{
mvc.pattern("/auth/reissue"),
mvc.pattern("/auth/guest/token"),
mvc.pattern(HttpMethod.GET, "/posts/{sharedUrl}"),
mvc.pattern(HttpMethod.GET, "/posts/shareUrl/{shareUrl}"),
mvc.pattern(HttpMethod.GET, "/posts/{postId}"),
mvc.pattern(HttpMethod.GET, "/posts/{postId}/comments"),
// mvc.pattern("/posts/{postId}/votes/guest/**"),
mvc.pattern("/auth/oauth2/**")
Expand Down
31 changes: 29 additions & 2 deletions src/main/java/com/swyp8team2/common/dev/DataInitializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
import com.swyp8team2.auth.application.jwt.TokenPair;
import com.swyp8team2.comment.domain.Comment;
import com.swyp8team2.comment.domain.CommentRepository;
import com.swyp8team2.common.annotation.ShareUrlCryptoService;
import com.swyp8team2.crypto.application.CryptoService;
import com.swyp8team2.image.domain.ImageFile;
import com.swyp8team2.image.domain.ImageFileRepository;
import com.swyp8team2.image.presentation.dto.ImageFileDto;
import com.swyp8team2.post.application.PostService;
import com.swyp8team2.post.domain.Post;
import com.swyp8team2.post.domain.PostImage;
import com.swyp8team2.post.domain.PostRepository;
Expand All @@ -16,6 +19,7 @@
import com.swyp8team2.user.domain.UserRepository;
import com.swyp8team2.vote.application.VoteService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -26,17 +30,38 @@

@Profile({"dev", "local"})
@Component
@RequiredArgsConstructor
public class DataInitializer {

private final NicknameAdjectiveRepository nicknameAdjectiveRepository;
private final UserRepository userRepository;
private final ImageFileRepository imageFileRepository;
private final PostRepository postRepository;
private final CryptoService shaereUrlCryptoService;
private final JwtService jwtService;
private final VoteService voteService;
private final CommentRepository commentRepository;

public DataInitializer(
NicknameAdjectiveRepository nicknameAdjectiveRepository,
UserRepository userRepository,
ImageFileRepository imageFileRepository,
PostRepository postRepository,
@ShareUrlCryptoService CryptoService shaereUrlCryptoService,
JwtService jwtService,
VoteService voteService,
CommentRepository commentRepository
) {
this.nicknameAdjectiveRepository = nicknameAdjectiveRepository;
this.userRepository = userRepository;
this.imageFileRepository = imageFileRepository;
this.postRepository = postRepository;
this.shaereUrlCryptoService = shaereUrlCryptoService;
this.jwtService = jwtService;
this.voteService = voteService;
this.commentRepository = commentRepository;
}


@Transactional
public void init() {
if (userRepository.count() > 0) {
Expand All @@ -56,7 +81,9 @@ public void init() {
for (int j = 0; j < 30; j += 2) {
ImageFile imageFile1 = imageFileRepository.save(ImageFile.create(new ImageFileDto("202502240006030.png", "https://image.photopic.site/images-dev/202502240006030.png", "https://image.photopic.site/images-dev/resized_202502240006030.png")));
ImageFile imageFile2 = imageFileRepository.save(ImageFile.create(new ImageFileDto("202502240006030.png", "https://image.photopic.site/images-dev/202502240006030.png", "https://image.photopic.site/images-dev/resized_202502240006030.png")));
posts.add(postRepository.save(Post.create(user.getId(), "description" + j, List.of(PostImage.create("뽀또A", imageFile1.getId()), PostImage.create("뽀또B", imageFile2.getId())), "https://photopic.site/shareurl")));
Post post = postRepository.save(Post.create(user.getId(), "description" + j, List.of(PostImage.create("뽀또A", imageFile1.getId()), PostImage.create("뽀또B", imageFile2.getId()))));
post.setShareUrl(shaereUrlCryptoService.encrypt(String.valueOf(post.getId())));
posts.add(post);
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public enum ErrorCode {
POST_IMAGE_NAME_GENERATOR_INDEX_OUT_OF_BOUND("이미지 이름 생성기 인덱스 초과"),
IMAGE_FILE_NOT_FOUND("존재하지 않는 이미지"),
POST_IMAGE_NOT_FOUND("게시글 이미지 없음"),
SHARE_URL_ALREADY_EXISTS("공유 URL이 이미 존재"),

//503
SERVICE_UNAVAILABLE("서비스 이용 불가"),
Expand Down
16 changes: 10 additions & 6 deletions src/main/java/com/swyp8team2/crypto/application/CryptoService.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,15 @@
import com.swyp8team2.common.exception.ErrorCode;
import com.swyp8team2.common.exception.InternalServerException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.apache.commons.codec.binary.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.util.Base64;

@Slf4j
@Service
public class CryptoService {

private static final String ALGORITHM = "AES";
Expand All @@ -31,7 +29,9 @@ public String encrypt(String data) {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] encryptedBytes = cipher.doFinal(data.getBytes());
return Base64.getEncoder().encodeToString(encryptedBytes);
return Base64.encodeBase64URLSafeString(encryptedBytes)
.replace('+', 'A')
.replace('/', 'B');
} catch (Exception e) {
log.error("encrypt error {}", e.getMessage());
throw new InternalServerException(ErrorCode.INTERNAL_SERVER_ERROR);
Expand All @@ -42,8 +42,12 @@ public String decrypt(String encryptedData) {
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedData));
return new String(decryptedBytes);
byte[] decoded = Base64.decodeBase64(
encryptedData
.replace('A', '+')
.replace('B', '/')
);
return new String(cipher.doFinal(decoded));
} catch (IllegalBlockSizeException | BadPaddingException e) {
log.debug("decrypt error {}", e.getMessage());
throw new BadRequestException(ErrorCode.INVALID_TOKEN);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ public String generate() {
if (index >= alphabets.length) {
throw new InternalServerException(ErrorCode.POST_IMAGE_NAME_GENERATOR_INDEX_OUT_OF_BOUND);
}
return "뽀또" + alphabets[index++];
return "뽀또 " + alphabets[index++];
}
}
28 changes: 26 additions & 2 deletions src/main/java/com/swyp8team2/post/application/PostService.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.swyp8team2.post.application;

import com.swyp8team2.common.annotation.ShareUrlCryptoService;
import com.swyp8team2.common.dto.CursorBasePaginatedResponse;
import com.swyp8team2.common.exception.BadRequestException;
import com.swyp8team2.common.exception.ErrorCode;
import com.swyp8team2.common.exception.InternalServerException;
import com.swyp8team2.crypto.application.CryptoService;
import com.swyp8team2.image.domain.ImageFile;
import com.swyp8team2.image.domain.ImageFileRepository;
import com.swyp8team2.post.domain.Post;
Expand All @@ -29,20 +31,37 @@

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class PostService {

private final PostRepository postRepository;
private final UserRepository userRepository;
private final RatioCalculator ratioCalculator;
private final ImageFileRepository imageFileRepository;
private final VoteRepository voteRepository;
private final CryptoService shareUrlCryptoService;

public PostService(
PostRepository postRepository,
UserRepository userRepository,
RatioCalculator ratioCalculator,
ImageFileRepository imageFileRepository,
VoteRepository voteRepository,
@ShareUrlCryptoService CryptoService shareUrlCryptoService
) {
this.postRepository = postRepository;
this.userRepository = userRepository;
this.ratioCalculator = ratioCalculator;
this.imageFileRepository = imageFileRepository;
this.voteRepository = voteRepository;
this.shareUrlCryptoService = shareUrlCryptoService;
}

@Transactional
public Long create(Long userId, CreatePostRequest request) {
List<PostImage> postImages = createPostImages(request);
Post post = Post.create(userId, request.description(), postImages, "TODO: location");
Post post = Post.create(userId, request.description(), postImages);
Post save = postRepository.save(post);
save.setShareUrl(shareUrlCryptoService.encrypt(String.valueOf(save.getId())));
return save.getId();
}

Expand Down Expand Up @@ -148,4 +167,9 @@ public void close(Long userId, Long postId) {
.orElseThrow(() -> new BadRequestException(ErrorCode.POST_NOT_FOUND));
post.close(userId);
}

public PostResponse findByShareUrl(Long userId, String shareUrl) {
String decrypt = shareUrlCryptoService.decrypt(shareUrl);
return findById(userId, Long.valueOf(decrypt));
}
}
Loading
Loading