diff --git a/README.md b/README.md new file mode 100644 index 0000000..3eec55b --- /dev/null +++ b/README.md @@ -0,0 +1,99 @@ +# Spring Rest Api 만들기 프로젝트 + +### 0. 개요 +- SpringBoot2 framework 기반에서 RESTful api 서비스를 Step by Step으로 만들어 나가는 프로젝트 +- daddyprogrammer.org에서 연재 및 소스 Github 등록 + - https://daddyprogrammer.org/post/series/springboot2%EB%A1%9C-rest-api-%EB%A7%8C%EB%93%A4%EA%B8%B0/ + +### 1. 개발환경 +- Java 8~11 +- SpringBoot 2.x +- SpringSecurity 5.x +- JPA, H2 +- Intellij Community + +### 2. 프로젝트 실행 +- H2 database 설치 + - https://www.h2database.com/html/download.html +- intellij lombok 플러그인 설치 + - Preferences -> Plugins -> Browse repositories... -> search lombok -> Install "IntelliJ Lombok plugin" +- Enable annotation processing + - Preferences - Annotation Procesors - Enable annotation processing 체크 +- build.gradle에 lombok 추가(Git을 받은경우 이미 추가되어있음) + - compileOnly 'org.projectlombok:lombok:1.16.16' +- 실행 + - Run -> SpringBootApiApplication +- Swagger + - http://localhost:8080/swagger-ui.html + +### 3. DDL +create table user ( + msrl bigint not null auto_increment, + name varchar(100) not null, + password varchar(100), + provider varchar(100), + uid varchar(50) not null, + primary key (msrl) + ) engine=InnoDB; + +create table user_roles ( + user_msrl bigint not null, + roles varchar(255) + ) engine=InnoDB; + + +alter table user +add constraint UK_a7hlm8sj8kmijx6ucp7wfyt31 unique (uid); + +alter table user_roles + add constraint FKel3d4qj41g0sy1mtp4sh055g7 + foreign key (user_msrl) + references user (msrl); + +### 4. 목차 +- SpringBoot2로 Rest api 만들기(1) – Intellij Community에서 프로젝트생성 + - Document + - https://daddyprogrammer.org/post/19/spring-boot1-start-intellij/ +- SpringBoot2로 Rest api 만들기(2) – HelloWorld + - Document + - https://daddyprogrammer.org/post/41/spring-boot2-helloworld/ +- SpringBoot2로 Rest api 만들기(3) – H2 Database 연동 + - Document + - https://daddyprogrammer.org/post/152/spring-boot2-h2-database-intergrate/ + - Git + - https://github.com/codej99/SpringRestApi/tree/feature/h2 +- SpringBoot2로 Rest api 만들기(4) – Swagger API 문서 자동화 + - Document + - https://daddyprogrammer.org/post/313/swagger-api-doc/ + - Git + - https://github.com/codej99/SpringRestApi/tree/feature/swagger +- SpringBoot2로 Rest api 만들기(5) – API 인터페이스 및 결과 데이터 구조 설계 + - Document + - https://daddyprogrammer.org/post/404/spring-boot2-5-design-api-interface-and-data-structure/ + - Git + - https://github.com/codej99/SpringRestApi/tree/feature/api-structure +- SpringBoot2로 Rest api 만들기(6) – ControllerAdvice를 이용한 Exception처리 + - Document + - https://daddyprogrammer.org/post/446/spring-boot2-5-exception-handling/ + - Git + - https://github.com/codej99/SpringRestApi/tree/feature/controller-advice +- SpringBoot2로 Rest api 만들기(7) – MessageSource를 이용한 Exception 처리 + - Document + - https://daddyprogrammer.org/post/499/springboot2-message-exception-handling-with-controlleradvice/ + - Git + - https://github.com/codej99/SpringRestApi/tree/feature/messagesource +- SpringBoot2로 Rest api 만들기(8) – SpringSecurity를 이용한 인증 및 권한부여 + - Document + - https://daddyprogrammer.org/post/636/springboot2-springsecurity-authentication-authorization/ + - Git + - https://github.com/codej99/SpringRestApi/tree/feature/security +- SpringBoot2로 Rest api 만들기(9) – Unit Test + - Document + - https://daddyprogrammer.org/post/938/springboot2-restapi-unit-test/ + - Git + - https://github.com/codej99/SpringRestApi/tree/feature/junit-test +- SpringBoot2로 Rest api 만들기(10) – Social Login kakao + - Document + - https://daddyprogrammer.org/post/1012/springboot2-rest-api-social-login-kakao/ + - Git + - https://github.com/codej99/SpringRestApi/tree/feature/social-kakao \ No newline at end of file diff --git a/build.gradle b/build.gradle index 8eff071..797f558 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,7 @@ plugins { id 'org.springframework.boot' version '2.1.4.RELEASE' id 'java' + id "org.sonarqube" version "2.7" } apply plugin: 'io.spring.dependency-management' @@ -28,9 +29,11 @@ dependencies { implementation 'io.springfox:springfox-swagger2:2.6.1' implementation 'io.springfox:springfox-swagger-ui:2.6.1' implementation 'net.rakugakibox.util:yaml-resource-bundle:1.1' + implementation 'com.google.code.gson:gson' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' runtimeOnly 'mysql:mysql-connector-java' annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.security:spring-security-test' testImplementation 'org.springframework.boot:spring-boot-starter-test' } diff --git a/src/main/java/com/rest/api/SpringRestApiApplication.java b/src/main/java/com/rest/api/SpringRestApiApplication.java index 5718fc4..64ad4d3 100644 --- a/src/main/java/com/rest/api/SpringRestApiApplication.java +++ b/src/main/java/com/rest/api/SpringRestApiApplication.java @@ -5,6 +5,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.web.client.RestTemplate; @SpringBootApplication public class SpringRestApiApplication { @@ -16,4 +17,9 @@ public class SpringRestApiApplication { public PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } + + @Bean + public RestTemplate getRestTemplate() { + return new RestTemplate(); + } } diff --git a/src/main/java/com/rest/api/advice/ExceptionAdvice.java b/src/main/java/com/rest/api/advice/ExceptionAdvice.java index c0b475a..8ff576d 100644 --- a/src/main/java/com/rest/api/advice/ExceptionAdvice.java +++ b/src/main/java/com/rest/api/advice/ExceptionAdvice.java @@ -1,8 +1,6 @@ package com.rest.api.advice; -import com.rest.api.advice.exception.CAuthenticationEntryPointException; -import com.rest.api.advice.exception.CEmailSigninFailedException; -import com.rest.api.advice.exception.CUserNotFoundException; +import com.rest.api.advice.exception.*; import com.rest.api.model.response.CommonResult; import com.rest.api.service.ResponseService; import lombok.RequiredArgsConstructor; @@ -51,10 +49,22 @@ public class ExceptionAdvice { @ExceptionHandler(AccessDeniedException.class) @ResponseStatus(HttpStatus.UNAUTHORIZED) - public CommonResult AccessDeniedException(HttpServletRequest request, AccessDeniedException e) { + public CommonResult accessDeniedException(HttpServletRequest request, AccessDeniedException e) { return responseService.getFailResult(Integer.valueOf(getMessage("accessDenied.code")), getMessage("accessDenied.msg")); } + @ExceptionHandler(CCommunicationException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public CommonResult communicationException(HttpServletRequest request, CCommunicationException e) { + return responseService.getFailResult(Integer.valueOf(getMessage("communicationError.code")), getMessage("communicationError.msg")); + } + + @ExceptionHandler(CUserExistException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public CommonResult communicationException(HttpServletRequest request, CUserExistException e) { + return responseService.getFailResult(Integer.valueOf(getMessage("existingUser.code")), getMessage("existingUser.msg")); + } + // code정보에 해당하는 메시지를 조회합니다. private String getMessage(String code) { return getMessage(code, null); diff --git a/src/main/java/com/rest/api/advice/exception/CCommunicationException.java b/src/main/java/com/rest/api/advice/exception/CCommunicationException.java new file mode 100644 index 0000000..ef60d0c --- /dev/null +++ b/src/main/java/com/rest/api/advice/exception/CCommunicationException.java @@ -0,0 +1,15 @@ +package com.rest.api.advice.exception; + +public class CCommunicationException extends RuntimeException { + public CCommunicationException(String msg, Throwable t) { + super(msg, t); + } + + public CCommunicationException(String msg) { + super(msg); + } + + public CCommunicationException() { + super(); + } +} diff --git a/src/main/java/com/rest/api/advice/exception/CUserExistException.java b/src/main/java/com/rest/api/advice/exception/CUserExistException.java new file mode 100644 index 0000000..ff97170 --- /dev/null +++ b/src/main/java/com/rest/api/advice/exception/CUserExistException.java @@ -0,0 +1,15 @@ +package com.rest.api.advice.exception; + +public class CUserExistException extends RuntimeException { + public CUserExistException(String msg, Throwable t) { + super(msg, t); + } + + public CUserExistException(String msg) { + super(msg); + } + + public CUserExistException() { + super(); + } +} diff --git a/src/main/java/com/rest/api/config/MessageConfiguration.java b/src/main/java/com/rest/api/config/MessageConfiguration.java index ce36020..bff452a 100644 --- a/src/main/java/com/rest/api/config/MessageConfiguration.java +++ b/src/main/java/com/rest/api/config/MessageConfiguration.java @@ -1,6 +1,5 @@ package com.rest.api.config; -import lombok.extern.slf4j.Slf4j; import net.rakugakibox.util.YamlResourceBundle; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.MessageSource; @@ -14,7 +13,6 @@ import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; import org.springframework.web.servlet.i18n.SessionLocaleResolver; import java.util.Locale; -import java.util.MissingResourceException; import java.util.ResourceBundle; @Configuration @@ -56,7 +54,7 @@ public class MessageConfiguration implements WebMvcConfigurer { // locale 정보에 따라 다른 yml 파일을 읽도록 처리 private static class YamlMessageSource extends ResourceBundleMessageSource { @Override - protected ResourceBundle doGetBundle(String basename, Locale locale) throws MissingResourceException { + protected ResourceBundle doGetBundle(String basename, Locale locale) { return ResourceBundle.getBundle(basename, locale, YamlResourceBundle.Control.INSTANCE); } } diff --git a/src/main/java/com/rest/api/config/security/CustomAccessDeniedHandler.java b/src/main/java/com/rest/api/config/security/CustomAccessDeniedHandler.java index 6a59be1..b39dbd4 100644 --- a/src/main/java/com/rest/api/config/security/CustomAccessDeniedHandler.java +++ b/src/main/java/com/rest/api/config/security/CustomAccessDeniedHandler.java @@ -1,14 +1,10 @@ package com.rest.api.config.security; import lombok.extern.slf4j.Slf4j; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.stereotype.Component; -import javax.servlet.RequestDispatcher; -import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @@ -17,11 +13,8 @@ import java.io.IOException; @Component public class CustomAccessDeniedHandler implements AccessDeniedHandler { - private static final Logger logger = LoggerFactory.getLogger(CustomAccessDeniedHandler.class); - - @Override - public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception) throws IOException, - ServletException { - response.sendRedirect("/exception/accessdenied"); - } + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception) throws IOException { + response.sendRedirect("/exception/accessdenied"); + } } diff --git a/src/main/java/com/rest/api/config/security/JwtTokenProvider.java b/src/main/java/com/rest/api/config/security/JwtTokenProvider.java index 7c32aa1..a283f71 100644 --- a/src/main/java/com/rest/api/config/security/JwtTokenProvider.java +++ b/src/main/java/com/rest/api/config/security/JwtTokenProvider.java @@ -22,7 +22,7 @@ import java.util.List; @Component public class JwtTokenProvider { // JWT 토큰을 생성 및 검증 모듈 - @Value("spring.jwt.secret") + @Value("${spring.jwt.secret}") private String secretKey; private long tokenValidMilisecond = 1000L * 60 * 60; // 1시간만 토큰 유효 diff --git a/src/main/java/com/rest/api/config/security/SecurityConfiguration.java b/src/main/java/com/rest/api/config/security/SecurityConfiguration.java index aaf5ba5..ed76551 100644 --- a/src/main/java/com/rest/api/config/security/SecurityConfiguration.java +++ b/src/main/java/com/rest/api/config/security/SecurityConfiguration.java @@ -31,7 +31,7 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // jwt token으로 인증할것이므로 세션필요없으므로 생성안함. .and() .authorizeRequests() // 다음 리퀘스트에 대한 사용권한 체크 - .antMatchers("/*/signin", "/*/signup").permitAll() // 가입 및 인증 주소는 누구나 접근가능 + .antMatchers("/*/signin", "/*/signin/**", "/*/signup", "/*/signup/**", "/social/**").permitAll() // 가입 및 인증 주소는 누구나 접근가능 .antMatchers(HttpMethod.GET, "/exception/**","/helloworld/**").permitAll() // hellowworld로 시작하는 GET요청 리소스는 누구나 접근가능 .antMatchers("/*/users").hasRole("ADMIN") .anyRequest().hasRole("USER") // 그외 나머지 요청은 모두 인증된 회원만 접근 가능 diff --git a/src/main/java/com/rest/api/controller/HelloController.java b/src/main/java/com/rest/api/controller/HelloController.java index 22f68dd..0cc567c 100644 --- a/src/main/java/com/rest/api/controller/HelloController.java +++ b/src/main/java/com/rest/api/controller/HelloController.java @@ -2,13 +2,17 @@ package com.rest.api.controller; import lombok.Getter; import lombok.Setter; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; +@Slf4j @Controller public class HelloController { + private static final String HELLO = "helloworld"; + @Setter @Getter public static class Hello { @@ -18,19 +22,21 @@ public class HelloController { @GetMapping(value = "/helloworld/string") @ResponseBody public String helloworldString() { - return "helloworld"; + log.debug("Helloworld"); + log.info("Helloworld"); + return HELLO; } @GetMapping(value = "/helloworld/json") @ResponseBody public Hello helloworldJson() { Hello hello = new Hello(); - hello.message = "helloworld"; + hello.message = HELLO; return hello; } @GetMapping(value = "/helloworld/page") public String helloworld() { - return "helloworld"; + return HELLO; } } diff --git a/src/main/java/com/rest/api/controller/common/SocialController.java b/src/main/java/com/rest/api/controller/common/SocialController.java new file mode 100644 index 0000000..2a400ea --- /dev/null +++ b/src/main/java/com/rest/api/controller/common/SocialController.java @@ -0,0 +1,60 @@ +package com.rest.api.controller.common; + +import com.google.gson.Gson; +import com.rest.api.service.social.KakaoService; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.servlet.ModelAndView; + +@RequiredArgsConstructor +@Controller +@RequestMapping("/social/login") +public class SocialController { + + private final Environment env; + private final RestTemplate restTemplate; + private final Gson gson; + private final KakaoService kakaoService; + + @Value("${spring.url.base}") + private String baseUrl; + + @Value("${spring.social.kakao.client_id}") + private String kakaoClientId; + + @Value("${spring.social.kakao.redirect}") + private String kakaoRedirect; + + /** + * 카카오 로그인 페이지 + */ + @GetMapping + public ModelAndView socialLogin(ModelAndView mav) { + + StringBuilder loginUrl = new StringBuilder() + .append(env.getProperty("spring.social.kakao.url.login")) + .append("?client_id=").append(kakaoClientId) + .append("&response_type=code") + .append("&redirect_uri=").append(baseUrl).append(kakaoRedirect); + + mav.addObject("loginUrl", loginUrl); + mav.setViewName("social/login"); + return mav; + } + + /** + * 카카오 인증 완료 후 리다이렉트 화면 + */ + @GetMapping(value = "/kakao") + public ModelAndView redirectKakao(ModelAndView mav, @RequestParam String code) { + mav.addObject("authInfo", kakaoService.getKakaoTokenInfo(code)); + mav.setViewName("social/redirectKakao"); + return mav; + } +} diff --git a/src/main/java/com/rest/api/controller/v1/SignController.java b/src/main/java/com/rest/api/controller/v1/SignController.java index 54b6cf6..b77949c 100644 --- a/src/main/java/com/rest/api/controller/v1/SignController.java +++ b/src/main/java/com/rest/api/controller/v1/SignController.java @@ -1,23 +1,25 @@ package com.rest.api.controller.v1; import com.rest.api.advice.exception.CEmailSigninFailedException; -import com.rest.api.entity.User; +import com.rest.api.advice.exception.CUserExistException; +import com.rest.api.advice.exception.CUserNotFoundException; import com.rest.api.config.security.JwtTokenProvider; +import com.rest.api.entity.User; import com.rest.api.model.response.CommonResult; import com.rest.api.model.response.SingleResult; +import com.rest.api.model.social.KakaoProfile; import com.rest.api.repo.UserJpaRepo; import com.rest.api.service.ResponseService; +import com.rest.api.service.social.KakaoService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.util.Collections; +import java.util.Optional; @Api(tags = {"1. Sign"}) @RequiredArgsConstructor @@ -29,9 +31,10 @@ public class SignController { private final JwtTokenProvider jwtTokenProvider; private final ResponseService responseService; private final PasswordEncoder passwordEncoder; + private final KakaoService kakaoService; @ApiOperation(value = "로그인", notes = "이메일 회원 로그인을 한다.") - @GetMapping(value = "/signin") + @PostMapping(value = "/signin") public SingleResult signin(@ApiParam(value = "회원ID : 이메일", required = true) @RequestParam String id, @ApiParam(value = "비밀번호", required = true) @RequestParam String password) { @@ -42,9 +45,20 @@ public class SignController { return responseService.getSingleResult(jwtTokenProvider.createToken(String.valueOf(user.getMsrl()), user.getRoles())); } + @ApiOperation(value = "소셜 로그인", notes = "소셜 회원 로그인을 한다.") + @PostMapping(value = "/signin/{provider}") + public SingleResult signinByProvider( + @ApiParam(value = "서비스 제공자 provider", required = true, defaultValue = "kakao") @PathVariable String provider, + @ApiParam(value = "소셜 access_token", required = true) @RequestParam String accessToken) { + + KakaoProfile profile = kakaoService.getKakaoProfile(accessToken); + User user = userJpaRepo.findByUidAndProvider(String.valueOf(profile.getId()), provider).orElseThrow(CUserNotFoundException::new); + return responseService.getSingleResult(jwtTokenProvider.createToken(String.valueOf(user.getMsrl()), user.getRoles())); + } + @ApiOperation(value = "가입", notes = "회원가입을 한다.") - @GetMapping(value = "/signup") - public CommonResult signin(@ApiParam(value = "회원ID : 이메일", required = true) @RequestParam String id, + @PostMapping(value = "/signup") + public CommonResult signup(@ApiParam(value = "회원ID : 이메일", required = true) @RequestParam String id, @ApiParam(value = "비밀번호", required = true) @RequestParam String password, @ApiParam(value = "이름", required = true) @RequestParam String name) { @@ -56,4 +70,26 @@ public class SignController { .build()); return responseService.getSuccessResult(); } + + @ApiOperation(value = "소셜 계정 가입", notes = "소셜 계정 회원가입을 한다.") + @PostMapping(value = "/signup/{provider}") + public CommonResult signupProvider(@ApiParam(value = "서비스 제공자 provider", required = true, defaultValue = "kakao") @PathVariable String provider, + @ApiParam(value = "소셜 access_token", required = true) @RequestParam String accessToken, + @ApiParam(value = "이름", required = true) @RequestParam String name) { + + KakaoProfile profile = kakaoService.getKakaoProfile(accessToken); + Optional user = userJpaRepo.findByUidAndProvider(String.valueOf(profile.getId()), provider); + if (user.isPresent()) + throw new CUserExistException(); + + User inUser = User.builder() + .uid(String.valueOf(profile.getId())) + .provider(provider) + .name(name) + .roles(Collections.singletonList("ROLE_USER")) + .build(); + + userJpaRepo.save(inUser); + return responseService.getSuccessResult(); + } } diff --git a/src/main/java/com/rest/api/controller/v1/UserController.java b/src/main/java/com/rest/api/controller/v1/UserController.java index 6e91ad3..c5c4541 100644 --- a/src/main/java/com/rest/api/controller/v1/UserController.java +++ b/src/main/java/com/rest/api/controller/v1/UserController.java @@ -33,11 +33,11 @@ public class UserController { } @ApiImplicitParams({ - @ApiImplicitParam(name = "X-AUTH-TOKEN", value = "로그인 성공 후 access_token", required = false, dataType = "String", paramType = "header") + @ApiImplicitParam(name = "X-AUTH-TOKEN", value = "로그인 성공 후 access_token", required = true, dataType = "String", paramType = "header") }) @ApiOperation(value = "회원 단건 조회", notes = "회원번호(msrl)로 회원을 조회한다") @GetMapping(value = "/user") - public SingleResult findUserById(@ApiParam(value = "언어", defaultValue = "ko") @RequestParam String lang) { + public SingleResult findUser() { // SecurityContext에서 인증받은 회원의 정보를 얻어온다. Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); String id = authentication.getName(); diff --git a/src/main/java/com/rest/api/entity/User.java b/src/main/java/com/rest/api/entity/User.java index e4c0f4c..5156ad8 100644 --- a/src/main/java/com/rest/api/entity/User.java +++ b/src/main/java/com/rest/api/entity/User.java @@ -1,10 +1,7 @@ package com.rest.api.entity; import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; @@ -25,13 +22,15 @@ public class User implements UserDetails { @Id // pk @GeneratedValue(strategy = GenerationType.IDENTITY) private long msrl; - @Column(nullable = false, unique = true, length = 30) + @Column(nullable = false, unique = true, length = 50) private String uid; @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) - @Column(nullable = false, length = 100) + @Column(length = 100) private String password; @Column(nullable = false, length = 100) private String name; + @Column(length = 100) + private String provider; @ElementCollection(fetch = FetchType.EAGER) @Builder.Default diff --git a/src/main/java/com/rest/api/model/social/KakaoProfile.java b/src/main/java/com/rest/api/model/social/KakaoProfile.java new file mode 100644 index 0000000..91594f0 --- /dev/null +++ b/src/main/java/com/rest/api/model/social/KakaoProfile.java @@ -0,0 +1,22 @@ +package com.rest.api.model.social; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +public class KakaoProfile { + private Long id; + private Properties properties; + + @Getter + @Setter + @ToString + private static class Properties { + private String nickname; + private String thumbnail_image; + private String profile_image; + } +} diff --git a/src/main/java/com/rest/api/model/social/RetKakaoAuth.java b/src/main/java/com/rest/api/model/social/RetKakaoAuth.java new file mode 100644 index 0000000..5dbf0dc --- /dev/null +++ b/src/main/java/com/rest/api/model/social/RetKakaoAuth.java @@ -0,0 +1,14 @@ +package com.rest.api.model.social; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class RetKakaoAuth { + private String access_token; + private String token_type; + private String refresh_token; + private long expires_in; + private String scope; +} diff --git a/src/main/java/com/rest/api/repo/UserJpaRepo.java b/src/main/java/com/rest/api/repo/UserJpaRepo.java index 3a267ec..f555f94 100644 --- a/src/main/java/com/rest/api/repo/UserJpaRepo.java +++ b/src/main/java/com/rest/api/repo/UserJpaRepo.java @@ -7,4 +7,6 @@ import java.util.Optional; public interface UserJpaRepo extends JpaRepository { Optional findByUid(String email); + + Optional findByUidAndProvider(String uid, String provider); } diff --git a/src/main/java/com/rest/api/service/ResponseService.java b/src/main/java/com/rest/api/service/ResponseService.java index fdc196b..a86f7cf 100644 --- a/src/main/java/com/rest/api/service/ResponseService.java +++ b/src/main/java/com/rest/api/service/ResponseService.java @@ -12,7 +12,7 @@ public class ResponseService { // enum으로 api 요청 결과에 대한 code, message를 정의합니다. public enum CommonResponse { - SUCCESS(0, "성공하였습니디."); + SUCCESS(0, "성공하였습니다."); int code; String msg; diff --git a/src/main/java/com/rest/api/service/social/KakaoService.java b/src/main/java/com/rest/api/service/social/KakaoService.java new file mode 100644 index 0000000..0290b87 --- /dev/null +++ b/src/main/java/com/rest/api/service/social/KakaoService.java @@ -0,0 +1,70 @@ +package com.rest.api.service.social; + +import com.google.gson.Gson; +import com.rest.api.advice.exception.CCommunicationException; +import com.rest.api.model.social.KakaoProfile; +import com.rest.api.model.social.RetKakaoAuth; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.env.Environment; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +@RequiredArgsConstructor +@Service +public class KakaoService { + + private final RestTemplate restTemplate; + private final Environment env; + private final Gson gson; + + @Value("${spring.url.base}") + private String baseUrl; + + @Value("${spring.social.kakao.client_id}") + private String kakaoClientId; + + @Value("${spring.social.kakao.redirect}") + private String kakaoRedirect; + + public KakaoProfile getKakaoProfile(String accessToken) { + // Set header : Content-type: application/x-www-form-urlencoded + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + headers.set("Authorization", "Bearer " + accessToken); + + // Set http entity + HttpEntity> request = new HttpEntity<>(null, headers); + try { + // Request profile + ResponseEntity response = restTemplate.postForEntity(env.getProperty("spring.social.kakao.url.profile"), request, String.class); + if (response.getStatusCode() == HttpStatus.OK) + return gson.fromJson(response.getBody(), KakaoProfile.class); + } catch (Exception e) { + throw new CCommunicationException(); + } + throw new CCommunicationException(); + } + + public RetKakaoAuth getKakaoTokenInfo(String code) { + // Set header : Content-type: application/x-www-form-urlencoded + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + // Set parameter + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("grant_type", "authorization_code"); + params.add("client_id", kakaoClientId); + params.add("redirect_uri", baseUrl + kakaoRedirect); + params.add("code", code); + // Set http entity + HttpEntity> request = new HttpEntity<>(params, headers); + ResponseEntity response = restTemplate.postForEntity(env.getProperty("spring.social.kakao.url.token"), request, String.class); + if (response.getStatusCode() == HttpStatus.OK) { + return gson.fromJson(response.getBody(), RetKakaoAuth.class); + } + return null; + } +} \ No newline at end of file diff --git a/src/main/resources/application-alpha.yml b/src/main/resources/application-alpha.yml new file mode 100644 index 0000000..1a0d004 --- /dev/null +++ b/src/main/resources/application-alpha.yml @@ -0,0 +1,24 @@ +logging: + level: + root: warn + com.rest.api: info + path: /home/ec2-user/api/log + file: + max-history: 7 + +spring: + profiles: alpha + datasource: + url: jdbc:mysql://127.0.0.1:33060/daddyprogrammer?useUnicode=true&autoReconnect=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false + driver-class-name: com.mysql.cj.jdbc.Driver + username: happydaddy + password: daddy1004 + jpa: + database-platform: org.hibernate.dialect.MySQL5InnoDBDialect + properties.hibernate: + hbm2ddl.auto: none + format_sql: true + showSql: true + generate-ddl: false + url: + base: http://dev-api.daddyprogrammer.org \ No newline at end of file diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml new file mode 100644 index 0000000..3b3316c --- /dev/null +++ b/src/main/resources/application-local.yml @@ -0,0 +1,20 @@ +logging: + level: + root: warn + com.rest.api: debug + +spring: + profiles: local + datasource: + url: jdbc:h2:tcp://localhost/~/test + driver-class-name: org.h2.Driver + username: sa + jpa: + database-platform: org.hibernate.dialect.H2Dialect + properties.hibernate: + hbm2ddl.auto: update + format_sql: true + showSql: true + generate-ddl: true + url: + base: http://localhost:8080 diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index dce0ce2..c0e050f 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,17 +1,16 @@ -server: - port: 8080 - spring: - datasource: - url: jdbc:h2:tcp://localhost/~/test - driver-class-name: org.h2.Driver - username: sa - jpa: - database-platform: org.hibernate.dialect.H2Dialect - properties.hibernate.hbm2ddl.auto: update - showSql: true + profiles: + active: local # 디폴트 환경 messages: basename: i18n/exception encoding: UTF-8 + social: + kakao: + client_id: XXXXXXXXXXXXXXXXXXXXXXXXXX # 앱생성시 받은 REST API 키 + redirect: /social/login/kakao + url: + login: https://kauth.kakao.com/oauth/authorize + token: https://kauth.kakao.com/oauth/token + profile: https://kapi.kakao.com/v2/user/me jwt: - secret: govlepel@$& \ No newline at end of file + secret: govlepel@$& diff --git a/src/main/resources/i18n/exception_en.yml b/src/main/resources/i18n/exception_en.yml index 47db4aa..ff111c2 100644 --- a/src/main/resources/i18n/exception_en.yml +++ b/src/main/resources/i18n/exception_en.yml @@ -12,4 +12,10 @@ entryPointException: msg: "You do not have permission to access this resource." accessDenied: code: "-1003" - msg: "A resource that can not be accessed with the privileges it has." \ No newline at end of file + msg: "A resource that can not be accessed with the privileges it has." +communicationError: + code: "-1004" + msg: "An error occurred during communication." +existingUser: + code: "-1005" + msg: "You are an existing member." \ No newline at end of file diff --git a/src/main/resources/i18n/exception_ko.yml b/src/main/resources/i18n/exception_ko.yml index 7213648..e09b6dd 100644 --- a/src/main/resources/i18n/exception_ko.yml +++ b/src/main/resources/i18n/exception_ko.yml @@ -12,4 +12,10 @@ entryPointException: msg: "해당 리소스에 접근하기 위한 권한이 없습니다." accessDenied: code: "-1003" - msg: "보유한 권한으로 접근할수 없는 리소스 입니다." \ No newline at end of file + msg: "보유한 권한으로 접근할수 없는 리소스 입니다." +communicationError: + code: "-1004" + msg: "통신 중 오류가 발생하였습니다." +existingUser: + code: "-1005" + msg: "이미 가입한 회원입니다. 로그인을 해주십시오." \ No newline at end of file diff --git a/src/main/resources/templates/social/login.ftl b/src/main/resources/templates/social/login.ftl new file mode 100644 index 0000000..bb2f64a --- /dev/null +++ b/src/main/resources/templates/social/login.ftl @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/src/main/resources/templates/social/redirectKakao.ftl b/src/main/resources/templates/social/redirectKakao.ftl new file mode 100644 index 0000000..d5b67f1 --- /dev/null +++ b/src/main/resources/templates/social/redirectKakao.ftl @@ -0,0 +1,5 @@ +access_token : ${authInfo.access_token}
+token_type : ${authInfo.token_type}
+refresh_token : ${authInfo.refresh_token}
+expires_in : ${authInfo.expires_in}
+scope : ${authInfo.scope}
\ No newline at end of file diff --git a/src/test/java/com/rest/api/SpringRestApiApplicationTests.java b/src/test/java/com/rest/api/SpringRestApiApplicationTests.java deleted file mode 100644 index 607846f..0000000 --- a/src/test/java/com/rest/api/SpringRestApiApplicationTests.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.rest.api; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class SpringRestApiApplicationTests { - - @Test - public void contextLoads() { - } - -} diff --git a/src/test/java/com/rest/api/controller/HelloControllerTest.java b/src/test/java/com/rest/api/controller/HelloControllerTest.java new file mode 100644 index 0000000..e03c475 --- /dev/null +++ b/src/test/java/com/rest/api/controller/HelloControllerTest.java @@ -0,0 +1,52 @@ +package com.rest.api.controller; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@RunWith(SpringRunner.class) +@SpringBootTest +@AutoConfigureMockMvc +@Transactional +public class HelloControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Test + public void helloworldString() throws Exception { + mockMvc.perform(get("/helloworld/string")) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(content().contentType("text/plain;charset=UTF-8")) + .andExpect(content().string("helloworld")); + } + + @Test + public void helloworldJson() throws Exception { + mockMvc.perform(get("/helloworld/json")) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json;charset=utf-8")) + .andExpect(jsonPath("$.message").value("helloworld")); + } + + @Test + public void helloworldPage() throws Exception { + mockMvc.perform(get("/helloworld/page")) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(content().contentType("text/html;charset=UTF-8")) + .andExpect(view().name("helloworld")) + .andExpect(content().string("helloworld")); + } +} \ No newline at end of file diff --git a/src/test/java/com/rest/api/controller/v1/SignControllerTest.java b/src/test/java/com/rest/api/controller/v1/SignControllerTest.java new file mode 100644 index 0000000..b28322d --- /dev/null +++ b/src/test/java/com/rest/api/controller/v1/SignControllerTest.java @@ -0,0 +1,137 @@ +package com.rest.api.controller.v1; + +import com.rest.api.entity.User; +import com.rest.api.repo.UserJpaRepo; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Collections; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@RunWith(SpringRunner.class) +@SpringBootTest +@AutoConfigureMockMvc +@Transactional +public class SignControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private UserJpaRepo userJpaRepo; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Before + public void setUp() throws Exception { + userJpaRepo.save(User.builder().uid("happydaddy@naver.com").name("happydaddy").password(passwordEncoder.encode("1234")).roles(Collections.singletonList("ROLE_USER")).build()); + } + + @Test + public void signin() throws Exception { + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("id", "happydaddy@naver.com"); + params.add("password", "1234"); + mockMvc.perform(post("/v1/signin").params(params)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.msg").exists()) + .andExpect(jsonPath("$.data").exists()); + } + + @Test + public void signinFail() throws Exception { + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("id", "happydaddy@naver.com"); + params.add("password", "12345"); + mockMvc.perform(post("/v1/signin").params(params)) + .andDo(print()) + .andExpect(status().is5xxServerError()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.code").value(-1001)) + .andExpect(jsonPath("$.msg").exists()); + } + + @Test + public void signup() throws Exception { + long epochTime = LocalDateTime.now().atZone(ZoneId.systemDefault()).toEpochSecond(); + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("id", "happydaddy_" + epochTime + "@naver.com"); + params.add("password", "12345"); + params.add("name", "happydaddy_" + epochTime); + mockMvc.perform(post("/v1/signup").params(params)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.msg").exists()); + } + + @Test + public void signupFail() throws Exception { + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("id", "happydaddy@naver.com"); + params.add("password", "12345"); + params.add("name", "happydaddy"); + mockMvc.perform(post("/v1/signup").params(params)) + .andDo(print()) + .andExpect(status().is5xxServerError()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.code").value(-9999)); + } + + @Test + public void signInProviderFail() throws Exception { + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("accessToken", "XXXXXXXX"); + mockMvc.perform(post("/v1/signin/kakao").params(params)) + .andDo(print()) + .andExpect(status().is5xxServerError()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.code").value(-1004)); + } + + @Test @Ignore + public void signUpSocial() throws Exception { + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("accessToken", "HizF3ir9522bMW3shkO0x0T9zBdXFCW1WsF56Qo9dVsAAAFqMwTqHw"); + params.add("name", "kakaoKing!"); + mockMvc.perform(post("/v1/signup/kakao").params(params)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.code").value(0)); + } + + @Test @Ignore + public void signInSocial() throws Exception { + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("accessToken", "HizF3ir9522bMW3shkO0x0T9zBdXFCW1WsF56Qo9dVsAAAFqMwTqHw"); + mockMvc.perform(post("/v1/signin/kakao").params(params)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data").exists()); + } +} \ No newline at end of file diff --git a/src/test/java/com/rest/api/controller/v1/UserControllerTest.java b/src/test/java/com/rest/api/controller/v1/UserControllerTest.java new file mode 100644 index 0000000..a4d410a --- /dev/null +++ b/src/test/java/com/rest/api/controller/v1/UserControllerTest.java @@ -0,0 +1,140 @@ +package com.rest.api.controller.v1; + +import com.rest.api.entity.User; +import com.rest.api.repo.UserJpaRepo; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.json.JacksonJsonParser; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import java.util.Collections; +import java.util.Optional; + +import static org.junit.Assert.assertTrue; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@RunWith(SpringRunner.class) +@SpringBootTest +@AutoConfigureMockMvc +@Transactional +public class UserControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private UserJpaRepo userJpaRepo; + + @Autowired + private PasswordEncoder passwordEncoder; + + private String token; + + @Before + public void setUp() throws Exception { + userJpaRepo.save(User.builder().uid("happydaddy@naver.com").name("happydaddy").password(passwordEncoder.encode("1234")).roles(Collections.singletonList("ROLE_USER")).build()); + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("id", "happydaddy@naver.com"); + params.add("password", "1234"); + MvcResult result = mockMvc.perform(post("/v1/signin").params(params)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.msg").exists()) + .andExpect(jsonPath("$.data").exists()) + .andReturn(); + + String resultString = result.getResponse().getContentAsString(); + JacksonJsonParser jsonParser = new JacksonJsonParser(); + token = jsonParser.parseMap(resultString).get("data").toString(); + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void invalidToken() throws Exception { + mockMvc.perform(MockMvcRequestBuilders + .get("/v1/users") + .header("X-AUTH-TOKEN", "XXXXXXXXXX")) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(forwardedUrl("/exception/entrypoint")); + } + + @Test + @WithMockUser(username = "mockUser", roles = {"ADMIN"}) // 가상의 Mock 유저 대입 + public void accessdenied() throws Exception { + mockMvc.perform(MockMvcRequestBuilders + .get("/v1/users")) + //.header("X-AUTH-TOKEN", token)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(forwardedUrl("/exception/accessdenied")); + } + + @Test + public void findAllUser() throws Exception { + mockMvc.perform(MockMvcRequestBuilders + .get("/v1/users") + .header("X-AUTH-TOKEN", token)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.list").exists()); + } + + @Test + public void findUser() throws Exception { + mockMvc.perform(MockMvcRequestBuilders + .get("/v1/user") + .header("X-AUTH-TOKEN", token)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.data").exists()); + } + + @Test + public void modify() throws Exception { + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("msrl", "1"); + params.add("name", "행복전도사"); + mockMvc.perform(MockMvcRequestBuilders + .put("/v1/user") + .header("X-AUTH-TOKEN", token) + .params(params)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)); + } + + @Test + public void delete() throws Exception { + Optional user = userJpaRepo.findByUid("happydaddy@naver.com"); + assertTrue(user.isPresent()); + mockMvc.perform(MockMvcRequestBuilders + .delete("/v1/user/" + user.get().getMsrl()) + .header("X-AUTH-TOKEN", token)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)); + } +} \ No newline at end of file diff --git a/src/test/java/com/rest/api/repo/UserJpaRepoTest.java b/src/test/java/com/rest/api/repo/UserJpaRepoTest.java new file mode 100644 index 0000000..0d7a1a6 --- /dev/null +++ b/src/test/java/com/rest/api/repo/UserJpaRepoTest.java @@ -0,0 +1,46 @@ +package com.rest.api.repo; + +import com.rest.api.entity.User; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.Collections; +import java.util.Optional; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.*; + +@RunWith(SpringRunner.class) +@DataJpaTest +public class UserJpaRepoTest { + + @Autowired + private UserJpaRepo userJpaRepo; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Test + public void whenFindByUid_thenReturnUser() { + String uid = "angrydaddy@gmail.com"; + String name = "angrydaddy"; + // given + userJpaRepo.save(User.builder() + .uid(uid) + .password(passwordEncoder.encode("1234")) + .name(name) + .roles(Collections.singletonList("ROLE_USER")) + .build()); + // when + Optional user = userJpaRepo.findByUid(uid); + // then + assertNotNull(user);// user객체가 null이 아닌지 체크 + assertTrue(user.isPresent()); // user객체가 존재여부 true/false 체크 + assertEquals(user.get().getName(), name); // user객체의 name과 name변수 값이 같은지 체크 + assertThat(user.get().getName(), is(name)); // user객체의 name과 name변수 값이 같은지 체크 + } +} \ No newline at end of file diff --git a/src/test/java/com/rest/api/service/social/KakaoServiceTest.java b/src/test/java/com/rest/api/service/social/KakaoServiceTest.java new file mode 100644 index 0000000..a06288b --- /dev/null +++ b/src/test/java/com/rest/api/service/social/KakaoServiceTest.java @@ -0,0 +1,30 @@ +package com.rest.api.service.social; + +import com.rest.api.model.social.KakaoProfile; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.junit.Assert.*; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class KakaoServiceTest { + + @Autowired + private KakaoService kakaoService; + + @Test @Ignore + public void whenGetKakaoProfile_thenReturnProfile() { + + String accessToken = "xjsMzpQtIr4w13FIQvL3R7BW7X4yvm1KmzXCTwopyWAAAAFqMxEcwA"; + // given + KakaoProfile profile = kakaoService.getKakaoProfile(accessToken); + // then + assertNotNull(profile); + assertEquals(profile.getId(), Long.valueOf(1066788171)); + } +} \ No newline at end of file