Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 공유보드 조회, 공유 보관함 사진 상세조회 , 공유 URL 생성 API #60

Open
wants to merge 26 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b3341c7
feat: springboot security oauth2 google authorization 설정 추가, JWT/Cook…
gwanhyeon Apr 16, 2021
6516d03
refactor: 전체적인 리팩토링
kouz95 Apr 16, 2021
5b0b6db
Merge branch 'feature/6' of https://github.com/DDD-5/null-moodof-back…
gwanhyeon Apr 16, 2021
89fbe8c
Merge branch 'develop' of https://github.com/DDD-5/null-moodof-backen…
gwanhyeon Apr 24, 2021
4cc6d3d
Merge branch 'develop' of https://github.com/DDD-5/null-moodof-backen…
gwanhyeon Apr 29, 2021
091335f
Merge branch 'develop' of https://github.com/DDD-5/null-moodof-backen…
gwanhyeon May 1, 2021
4feadb5
Merge branch 'develop' of https://github.com/DDD-5/null-moodof-backen…
gwanhyeon May 6, 2021
1f25555
Merge branch 'develop' of https://github.com/DDD-5/null-moodof-backen…
gwanhyeon May 19, 2021
78fdaa8
feat: 상세 조회 태그 조건 추가 및 조회시 태그 없음 검색 추가
kouz95 May 22, 2021
72ce070
feat: BoardPhoto 순서 추가
kouz95 May 25, 2021
28f23de
feat: BoardPhoto 전체 조회 구현
kouz95 May 25, 2021
55d3be7
feat: storagePhoto 조회 사진 개수 추가
kouz95 May 25, 2021
2044485
Merge branch 'develop' of https://github.com/DDD-5/null-moodof-backen…
gwanhyeon May 27, 2021
f2862a6
fix: code conflict
gwanhyeon May 27, 2021
41344c8
feat: 공유 카테고리 URL생성, 공유 카테고리, 보드 리스트조회
gwanhyeon May 28, 2021
03a0c75
feat: 공유보드 조회, 공유 보관함 사진 상세조회 , 공유 URL 생성 API
gwanhyeon May 29, 2021
0495449
fix: 태그생성시 TagAttachment 생성시 TDD 공유 로직 수정
gwanhyeon May 29, 2021
d81572f
chore: profile dev ignore 추가
gwanhyeon May 29, 2021
ae231f1
fix: 불필요 코드 정리 및 삭제
gwanhyeon May 29, 2021
d45907a
fix: git ignore 불필요 코드 참조 삭제
gwanhyeon May 29, 2021
8f228f9
feat: 공유 URI AES256 -> SHA256 암호화 변경, 코드리뷰 사항 반영
gwanhyeon May 31, 2021
cad9ee6
fix: 공유 URI 응답 DTO 변경, 미사용 코드 제거
gwanhyeon May 31, 2021
9140ddf
Merge branch 'develop' into feature/48
gwanhyeon May 31, 2021
d85766b
refactor: 보드 공유 키 생성 저장 로직 수정
gwanhyeon Jun 13, 2021
7e9400b
refactor: 보드 공유키 조회 URI 변경
gwanhyeon Jun 13, 2021
dbf472f
Merge remote-tracking branch 'origin/feature/48' into feature/48
gwanhyeon Jun 13, 2021
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: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ out/
### VS Code ###
.vscode/

application-security.yml
application-security.yml
/src/main/resources/application-dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.ddd.moodof.adapter.infrastructure.configuration;

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties(prefix = "encrypt")
@Getter
@Setter
public class EncryptConfig {
private String key;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.firewall.StrictHttpFirewall;

@RequiredArgsConstructor
@Configuration
Expand Down Expand Up @@ -49,6 +52,19 @@ public void configure(AuthenticationManagerBuilder authenticationManagerBuilder)
.userDetailsService(customUserDetailsService)
.passwordEncoder(passwordEncoder());
}
@Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
web.httpFirewall(allowUrlEncodedSlashHttpFirewall());
}

@Bean
public HttpFirewall allowUrlEncodedSlashHttpFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowUrlEncodedSlash(true);
return firewall;
}


@Bean
public PasswordEncoder passwordEncoder() {
Expand Down Expand Up @@ -93,7 +109,8 @@ protected void configure(HttpSecurity http) throws Exception {
"/webjars/**",
"/v2/**",
"/swagger-resources/**",
"/api/public/**"
"/api/public/**",
"/api/public/**/**"
).permitAll()
.anyRequest()
.authenticated()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.ddd.moodof.adapter.infrastructure.configuration;

import com.ddd.moodof.adapter.infrastructure.security.LoginUserIdArgumentResolver;
import com.ddd.moodof.adapter.infrastructure.security.SharedBoardIdArgumentResolver;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
Expand All @@ -16,9 +17,12 @@ public class WebMvcConfig implements WebMvcConfigurer {

private final LoginUserIdArgumentResolver loginUserIdArgumentResolver;

private final SharedBoardIdArgumentResolver sharedBoardIdArgumentResolver;

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(loginUserIdArgumentResolver);
resolvers.add(sharedBoardIdArgumentResolver);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.ddd.moodof.adapter.infrastructure.security;

import com.ddd.moodof.adapter.infrastructure.configuration.EncryptConfig;

import com.ddd.moodof.adapter.infrastructure.security.encrypt.EncryptUtil;
import com.ddd.moodof.adapter.presentation.SharedBoardId;
import com.ddd.moodof.domain.model.board.BoardRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

@RequiredArgsConstructor
@Component
public class SharedBoardIdArgumentResolver implements HandlerMethodArgumentResolver {

private final BoardRepository boardRepository;

private final EncryptConfig encryptConfig;

@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(SharedBoardId.class);
}

@Override
public Long resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

SharedBoardId customParam = parameter.getParameterAnnotation(SharedBoardId.class);
String key = webRequest.getParameter(customParam.value());

try {
String id = EncryptUtil.decryptAES256(key, encryptConfig.getKey());
Long boardId = Long.valueOf(id);
boardRepository.findById(boardId)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 id = " + id));
return boardId;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.ddd.moodof.adapter.infrastructure.security.encrypt;

import org.springframework.stereotype.Component;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.security.AlgorithmParameters;
import java.security.SecureRandom;
import java.util.Base64;

@Component
public class EncryptUtil {

public static String encryptAES256(String msg, String key) throws Exception {
SecureRandom random = new SecureRandom();
byte bytes[] = new byte[20];
random.nextBytes(bytes);
byte[] saltBytes = bytes;
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
PBEKeySpec spec = new PBEKeySpec(key.toCharArray(), saltBytes, 70000, 256);
SecretKey secretKey = factory.generateSecret(spec);
SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES");

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();

byte[] ivBytes = params.getParameterSpec(IvParameterSpec.class).getIV();
byte[] encryptedTextBytes = cipher.doFinal(msg.getBytes("UTF-8"));
byte[] buffer = new byte[saltBytes.length + ivBytes.length + encryptedTextBytes.length];
System.arraycopy(saltBytes, 0, buffer, 0, saltBytes.length);
System.arraycopy(ivBytes, 0, buffer, saltBytes.length, ivBytes.length);
System.arraycopy(encryptedTextBytes, 0, buffer, saltBytes.length + ivBytes.length, encryptedTextBytes.length);
// .replaceAll("[^0-9a-zA-Z]", "");
return Base64.getEncoder().encodeToString(buffer).replaceAll("\\/","%");
}
public static String decryptAES256(String msg, String key) throws Exception {
msg = msg.replaceAll("%","/");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
ByteBuffer buffer = ByteBuffer.wrap(Base64.getDecoder().decode(msg));
byte[] saltBytes = new byte[20];
byte[] ivBytes = new byte[cipher.getBlockSize()];
byte[] encryoptedTextBytes = new byte[buffer.capacity() - saltBytes.length - ivBytes.length];

buffer.get(saltBytes, 0, saltBytes.length);
buffer.get(ivBytes, 0, ivBytes.length);
buffer.get(encryoptedTextBytes);

SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
PBEKeySpec spec = new PBEKeySpec(key.toCharArray(), saltBytes, 70000, 256);
SecretKey secretKey = factory.generateSecret(spec);
SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(ivBytes));
byte[] decryptedTextBytes = cipher.doFinal(encryoptedTextBytes);
return new String(decryptedTextBytes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import com.ddd.moodof.adapter.presentation.api.BoardAPI;
import com.ddd.moodof.application.BoardService;
import com.ddd.moodof.application.dto.BoardDTO;
import com.ddd.moodof.application.dto.SharedDTO;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.net.URI;

@RequiredArgsConstructor
Expand Down Expand Up @@ -44,4 +46,10 @@ public ResponseEntity<BoardDTO.BoardResponse> delete(@LoginUserId Long userId, @
boardService.delete(userId, id);
return ResponseEntity.noContent().build();
}

@PostMapping("/shared")
public ResponseEntity<BoardDTO.BoardSharedResponse> create(@RequestBody BoardDTO.BoardSharedRequest request, @LoginUserId Long userId, HttpServletRequest httpServletRequest){
BoardDTO.BoardSharedResponse response = boardService.createSharedKey(request.getId(), userId, httpServletRequest);
return ResponseEntity.created(URI.create(API_BOARD + "/shared/" + response.getId())).body(response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.ddd.moodof.adapter.presentation;

import com.ddd.moodof.adapter.presentation.api.PublicAPI;
import com.ddd.moodof.application.BoardPhotoService;
import com.ddd.moodof.application.StoragePhotoService;
import com.ddd.moodof.application.dto.BoardPhotoDTO;
import com.ddd.moodof.application.dto.StoragePhotoDTO;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RequiredArgsConstructor
@RequestMapping(PublicController.API_PUBLIC)
@RestController
public class PublicController implements PublicAPI {

public static final String API_PUBLIC = "/api/public";

private final StoragePhotoService storagePhotoService;

private final BoardPhotoService boardPhotoService;

@Override
@GetMapping("/boards/{sharedKey}")
public ResponseEntity<List<BoardPhotoDTO.BoardPhotoResponse>> findAllByBoard(@PathVariable String sharedKey) {
List<BoardPhotoDTO.BoardPhotoResponse> responses = boardPhotoService.findAllBySharedKey(sharedKey);
return ResponseEntity.ok(responses);
}

@GetMapping("/boards/{sharedKey}/detail/{id}")
public ResponseEntity<StoragePhotoDTO.StoragePhotoDetailResponse> getSharedBoardDetail(@PathVariable String sharedKey,@PathVariable Long id){
StoragePhotoDTO.StoragePhotoDetailResponse response = storagePhotoService.findSharedBoardDetail(sharedKey, id);
return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.ddd.moodof.adapter.presentation;

import java.lang.annotation.*;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SharedBoardId {
String value() default "";
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

import com.ddd.moodof.adapter.presentation.LoginUserId;
import com.ddd.moodof.application.dto.BoardDTO;
import com.ddd.moodof.application.dto.SharedDTO;
import io.swagger.annotations.ApiImplicitParam;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import springfox.documentation.annotations.ApiIgnore;

import javax.servlet.http.HttpServletRequest;

public interface BoardAPI {
@ApiImplicitParam(name = "Authorization", value = "Access Token", required = true, paramType = "header", dataTypeClass = String.class, example = "Bearer access_token")
@PostMapping
Expand All @@ -23,4 +26,8 @@ public interface BoardAPI {
@ApiImplicitParam(name = "Authorization", value = "Access Token", required = true, paramType = "header", dataTypeClass = String.class, example = "Bearer access_token")
@DeleteMapping("/{id}")
ResponseEntity<BoardDTO.BoardResponse> delete(@ApiIgnore @LoginUserId Long userId, @PathVariable Long id);

@ApiImplicitParam(name = "Authorization", value = "Access Token", required = true, paramType = "header", dataTypeClass = String.class, example = "Bearer access_token")
@PostMapping("/shared")
ResponseEntity<BoardDTO.BoardSharedResponse> create(@RequestBody BoardDTO.BoardSharedRequest request, @LoginUserId Long userId, HttpServletRequest httpServletRequest);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.ddd.moodof.adapter.presentation.api;
import com.ddd.moodof.adapter.presentation.LoginUserId;
import com.ddd.moodof.application.dto.BoardPhotoDTO;
import com.ddd.moodof.application.dto.CategoryDTO;
import com.ddd.moodof.application.dto.StoragePhotoDTO;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

public interface PublicAPI {
@GetMapping("/boards/{sharedKey}")
ResponseEntity<List<BoardPhotoDTO.BoardPhotoResponse>> findAllByBoard(@PathVariable String sharedKey);

@GetMapping("/boards/{sharedKey}/detail/{id}")
ResponseEntity<StoragePhotoDTO.StoragePhotoDetailResponse> getSharedBoardDetail(@PathVariable String sharedKey,@PathVariable Long id);


}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.ddd.moodof.application.dto.BoardPhotoDTO;
import com.ddd.moodof.application.verifier.BoardPhotoVerifier;
import com.ddd.moodof.domain.model.board.Board;
import com.ddd.moodof.domain.model.board.BoardRepository;
import com.ddd.moodof.domain.model.board.photo.BoardPhoto;
import com.ddd.moodof.domain.model.board.photo.BoardPhotoRepository;
import lombok.RequiredArgsConstructor;
Expand All @@ -18,6 +20,7 @@ public class BoardPhotoService {

private final BoardPhotoRepository boardPhotoRepository;
private final BoardPhotoVerifier boardPhotoVerifier;
private final BoardRepository boardRepository;

public List<BoardPhotoDTO.BoardPhotoResponse> addPhotos(Long userId, BoardPhotoDTO.AddBoardPhoto request) {
// TODO: 2021/05/25 StoragePhoto 삭제 대응
Expand Down Expand Up @@ -57,4 +60,10 @@ public List<BoardPhotoDTO.BoardPhotoResponse> findAllByBoardId(Long boardId, Lon
List<BoardPhoto> boardPhotos = boardPhotoRepository.findAllByBoardIdAndUserId(boardId, userId);
return BoardPhotoDTO.BoardPhotoResponse.listFrom(boardPhotos);
}
public List<BoardPhotoDTO.BoardPhotoResponse> findAllBySharedKey(String sharedKey) {
Board board = boardRepository.findBySharedKey(sharedKey)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 sharedKey =" + sharedKey));
List<BoardPhoto> boardPhotos = boardPhotoRepository.findAllByBoardIdAndUserId(board.getId(), board.getUserId());
return BoardPhotoDTO.BoardPhotoResponse.listFrom(boardPhotos);
}
}
Loading