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
2 changes: 1 addition & 1 deletion server-config
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";
}
39 changes: 39 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,39 @@
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.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.encrypt.AesBytesEncryptor;

@Configuration
public class CryptoConfig {

private final String guestTokenSymmetricKey;
private final String shareUrlSymmetricKey;
private final String salt;

public CryptoConfig(
@Value("${crypto.secret-key.guest-token}") String guestTokenSymmetricKey,
@Value("${crypto.secret-key.share-url}") String shareUrlSymmetricKey,
@Value("${crypto.salt}") String salt
) {
this.guestTokenSymmetricKey = guestTokenSymmetricKey;
this.shareUrlSymmetricKey = shareUrlSymmetricKey;
this.salt = salt;
}

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

@ShareUrlCryptoService
@Bean(name = ShareUrlCryptoService.QUALIFIER)
public CryptoService shareUrlCryptoService() throws Exception {
return new CryptoService(new AesBytesEncryptor(shareUrlSymmetricKey, salt));
}
}
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
34 changes: 34 additions & 0 deletions src/main/java/com/swyp8team2/crypto/application/Base62.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.swyp8team2.crypto.application;

import java.math.BigInteger;

public class Base62 {

private static final String BASE62_ALPHABET =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
private static final int BASE = 62;

public static String encode(byte[] bytes) {
BigInteger value = new BigInteger(1, bytes);
StringBuilder encoded = new StringBuilder();

while (value.compareTo(BigInteger.ZERO) > 0) {
BigInteger[] divRem = value.divideAndRemainder(BigInteger.valueOf(BASE));
value = divRem[0];
encoded.insert(0, BASE62_ALPHABET.charAt(divRem[1].intValue()));
}

return encoded.toString();
}

public static byte[] decode(String encoded) {
BigInteger value = BigInteger.ZERO;

for (char c : encoded.toCharArray()) {
value = value.multiply(BigInteger.valueOf(BASE))
.add(BigInteger.valueOf(BASE62_ALPHABET.indexOf(c)));
}

return value.toByteArray();
}
}
43 changes: 13 additions & 30 deletions src/main/java/com/swyp8team2/crypto/application/CryptoService.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,36 @@
import com.swyp8team2.common.exception.BadRequestException;
import com.swyp8team2.common.exception.ErrorCode;
import com.swyp8team2.common.exception.InternalServerException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.security.crypto.encrypt.AesBytesEncryptor;

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

@Slf4j
@Service
@RequiredArgsConstructor
public class CryptoService {

private static final String ALGORITHM = "AES";
private final SecretKey secretKey;

public CryptoService() throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
keyGenerator.init(256);
this.secretKey = keyGenerator.generateKey();
}
private final AesBytesEncryptor encryptor;

public String encrypt(String data) {
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] encryptedBytes = cipher.doFinal(data.getBytes());
return Base64.getEncoder().encodeToString(encryptedBytes);
byte[] encrypt = encryptor.encrypt(data.getBytes(StandardCharsets.UTF_8));
return Base62.encode(encrypt);
} catch (Exception e) {
log.error("encrypt error {}", e.getMessage());
throw new InternalServerException(ErrorCode.INTERNAL_SERVER_ERROR);
log.debug("encrypt error {}", e.getMessage());
throw new BadRequestException(ErrorCode.INVALID_TOKEN);
}
}

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);
} catch (IllegalBlockSizeException | BadPaddingException e) {
byte[] decryptBytes = Base62.decode(encryptedData);
byte[] decrypt = encryptor.decrypt(decryptBytes);
return new String(decrypt, StandardCharsets.UTF_8);
} catch (Exception e) {
log.debug("decrypt error {}", e.getMessage());
throw new BadRequestException(ErrorCode.INVALID_TOKEN);
} catch (Exception e) {
log.error("decrypt error {}", e.getMessage());
throw new InternalServerException(ErrorCode.INTERNAL_SERVER_ERROR);
}
}
}
Loading