Merge branch 'master' into feature/security
This commit is contained in:
38
README.md
38
README.md
@@ -25,8 +25,32 @@
|
|||||||
- Run -> SpringBootApiApplication
|
- Run -> SpringBootApiApplication
|
||||||
- Swagger
|
- Swagger
|
||||||
- http://localhost:8080/swagger-ui.html
|
- 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);
|
||||||
|
|
||||||
### 3. 목차
|
alter table user_roles
|
||||||
|
add constraint FKel3d4qj41g0sy1mtp4sh055g7
|
||||||
|
foreign key (user_msrl)
|
||||||
|
references user (msrl);
|
||||||
|
|
||||||
|
### 4. 목차
|
||||||
- SpringBoot2로 Rest api 만들기(1) – Intellij Community에서 프로젝트생성
|
- SpringBoot2로 Rest api 만들기(1) – Intellij Community에서 프로젝트생성
|
||||||
- Document
|
- Document
|
||||||
- https://daddyprogrammer.org/post/19/spring-boot1-start-intellij/
|
- https://daddyprogrammer.org/post/19/spring-boot1-start-intellij/
|
||||||
@@ -62,4 +86,14 @@
|
|||||||
- Document
|
- Document
|
||||||
- https://daddyprogrammer.org/post/636/springboot2-springsecurity-authentication-authorization/
|
- https://daddyprogrammer.org/post/636/springboot2-springsecurity-authentication-authorization/
|
||||||
- Git
|
- Git
|
||||||
- https://github.com/codej99/SpringRestApi/tree/feature/security
|
- 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
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'org.springframework.boot' version '2.1.4.RELEASE'
|
id 'org.springframework.boot' version '2.1.4.RELEASE'
|
||||||
id 'java'
|
id 'java'
|
||||||
|
id "org.sonarqube" version "2.7"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'io.spring.dependency-management'
|
apply plugin: 'io.spring.dependency-management'
|
||||||
@@ -28,9 +29,11 @@ dependencies {
|
|||||||
implementation 'io.springfox:springfox-swagger2:2.6.1'
|
implementation 'io.springfox:springfox-swagger2:2.6.1'
|
||||||
implementation 'io.springfox:springfox-swagger-ui:2.6.1'
|
implementation 'io.springfox:springfox-swagger-ui:2.6.1'
|
||||||
implementation 'net.rakugakibox.util:yaml-resource-bundle:1.1'
|
implementation 'net.rakugakibox.util:yaml-resource-bundle:1.1'
|
||||||
|
implementation 'com.google.code.gson:gson'
|
||||||
compileOnly 'org.projectlombok:lombok'
|
compileOnly 'org.projectlombok:lombok'
|
||||||
runtimeOnly 'com.h2database:h2'
|
runtimeOnly 'com.h2database:h2'
|
||||||
runtimeOnly 'mysql:mysql-connector-java'
|
runtimeOnly 'mysql:mysql-connector-java'
|
||||||
annotationProcessor 'org.projectlombok:lombok'
|
annotationProcessor 'org.projectlombok:lombok'
|
||||||
|
testImplementation 'org.springframework.security:spring-security-test'
|
||||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
public class SpringRestApiApplication {
|
public class SpringRestApiApplication {
|
||||||
@@ -16,4 +17,9 @@ public class SpringRestApiApplication {
|
|||||||
public PasswordEncoder passwordEncoder() {
|
public PasswordEncoder passwordEncoder() {
|
||||||
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
|
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RestTemplate getRestTemplate() {
|
||||||
|
return new RestTemplate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package com.rest.api.advice;
|
package com.rest.api.advice;
|
||||||
|
|
||||||
import com.rest.api.advice.exception.CAuthenticationEntryPointException;
|
import com.rest.api.advice.exception.*;
|
||||||
import com.rest.api.advice.exception.CEmailSigninFailedException;
|
|
||||||
import com.rest.api.advice.exception.CUserNotFoundException;
|
|
||||||
import com.rest.api.model.response.CommonResult;
|
import com.rest.api.model.response.CommonResult;
|
||||||
import com.rest.api.service.ResponseService;
|
import com.rest.api.service.ResponseService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@@ -51,10 +49,22 @@ public class ExceptionAdvice {
|
|||||||
|
|
||||||
@ExceptionHandler(AccessDeniedException.class)
|
@ExceptionHandler(AccessDeniedException.class)
|
||||||
@ResponseStatus(HttpStatus.UNAUTHORIZED)
|
@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"));
|
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정보에 해당하는 메시지를 조회합니다.
|
// code정보에 해당하는 메시지를 조회합니다.
|
||||||
private String getMessage(String code) {
|
private String getMessage(String code) {
|
||||||
return getMessage(code, null);
|
return getMessage(code, null);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.rest.api.config;
|
package com.rest.api.config;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import net.rakugakibox.util.YamlResourceBundle;
|
import net.rakugakibox.util.YamlResourceBundle;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.MessageSource;
|
import org.springframework.context.MessageSource;
|
||||||
@@ -14,7 +13,6 @@ import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
|
|||||||
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
|
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.MissingResourceException;
|
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@@ -56,7 +54,7 @@ public class MessageConfiguration implements WebMvcConfigurer {
|
|||||||
// locale 정보에 따라 다른 yml 파일을 읽도록 처리
|
// locale 정보에 따라 다른 yml 파일을 읽도록 처리
|
||||||
private static class YamlMessageSource extends ResourceBundleMessageSource {
|
private static class YamlMessageSource extends ResourceBundleMessageSource {
|
||||||
@Override
|
@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);
|
return ResourceBundle.getBundle(basename, locale, YamlResourceBundle.Control.INSTANCE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package com.rest.api.config.security;
|
package com.rest.api.config.security;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.security.access.AccessDeniedException;
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
import org.springframework.security.web.access.AccessDeniedHandler;
|
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
@@ -17,12 +15,10 @@ import java.io.IOException;
|
|||||||
@Component
|
@Component
|
||||||
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
|
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,
|
||||||
@Override
|
|
||||||
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception) throws IOException,
|
|
||||||
ServletException {
|
ServletException {
|
||||||
RequestDispatcher dispatcher = request.getRequestDispatcher("/exception/accessdenied");
|
RequestDispatcher dispatcher = request.getRequestDispatcher("/exception/accessdenied");
|
||||||
dispatcher.forward(request, response);
|
dispatcher.forward(request, response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import java.util.List;
|
|||||||
@Component
|
@Component
|
||||||
public class JwtTokenProvider { // JWT 토큰을 생성 및 검증 모듈
|
public class JwtTokenProvider { // JWT 토큰을 생성 및 검증 모듈
|
||||||
|
|
||||||
@Value("spring.jwt.secret")
|
@Value("${spring.jwt.secret}")
|
||||||
private String secretKey;
|
private String secretKey;
|
||||||
|
|
||||||
private long tokenValidMilisecond = 1000L * 60 * 60; // 1시간만 토큰 유효
|
private long tokenValidMilisecond = 1000L * 60 * 60; // 1시간만 토큰 유효
|
||||||
|
|||||||
@@ -31,10 +31,9 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
|
|||||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // jwt token으로 인증할것이므로 세션필요없으므로 생성안함.
|
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // jwt token으로 인증할것이므로 세션필요없으므로 생성안함.
|
||||||
.and()
|
.and()
|
||||||
.authorizeRequests() // 다음 리퀘스트에 대한 사용권한 체크
|
.authorizeRequests() // 다음 리퀘스트에 대한 사용권한 체크
|
||||||
.antMatchers("/*/signin", "/*/signup").permitAll() // 가입 및 인증 주소는 누구나 접근가능
|
.antMatchers("/*/signin", "/*/signin/**", "/*/signup", "/*/signup/**", "/social/**").permitAll() // 가입 및 인증 주소는 누구나 접근가능
|
||||||
.antMatchers(HttpMethod.GET, "/helloworld/**").permitAll() // hellowworld로 시작하는 GET요청 리소스는 누구나 접근가능
|
.antMatchers(HttpMethod.GET, "/helloworld/**").permitAll() // hellowworld로 시작하는 GET요청 리소스는 누구나 접근가능
|
||||||
.antMatchers("/*/users").hasRole("ADMIN")
|
.anyRequest().hasRole("USER") // 그외 나머지 요청은 모두 인증된 회원만 접근 가능
|
||||||
.anyRequest().hasRole("USER") // 그외 나머지 요청은 모두 인증된 회원만 접근 가능
|
|
||||||
.and()
|
.and()
|
||||||
.exceptionHandling().accessDeniedHandler(new CustomAccessDeniedHandler())
|
.exceptionHandling().accessDeniedHandler(new CustomAccessDeniedHandler())
|
||||||
.and()
|
.and()
|
||||||
|
|||||||
@@ -2,13 +2,17 @@ package com.rest.api.controller;
|
|||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
@Controller
|
@Controller
|
||||||
public class HelloController {
|
public class HelloController {
|
||||||
|
|
||||||
|
private static final String HELLO = "helloworld";
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
@Getter
|
@Getter
|
||||||
public static class Hello {
|
public static class Hello {
|
||||||
@@ -18,19 +22,21 @@ public class HelloController {
|
|||||||
@GetMapping(value = "/helloworld/string")
|
@GetMapping(value = "/helloworld/string")
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public String helloworldString() {
|
public String helloworldString() {
|
||||||
return "helloworld";
|
log.debug("Helloworld");
|
||||||
|
log.info("Helloworld");
|
||||||
|
return HELLO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping(value = "/helloworld/json")
|
@GetMapping(value = "/helloworld/json")
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public Hello helloworldJson() {
|
public Hello helloworldJson() {
|
||||||
Hello hello = new Hello();
|
Hello hello = new Hello();
|
||||||
hello.message = "helloworld";
|
hello.message = HELLO;
|
||||||
return hello;
|
return hello;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping(value = "/helloworld/page")
|
@GetMapping(value = "/helloworld/page")
|
||||||
public String helloworld() {
|
public String helloworld() {
|
||||||
return "helloworld";
|
return HELLO;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,23 +1,25 @@
|
|||||||
package com.rest.api.controller.v1;
|
package com.rest.api.controller.v1;
|
||||||
|
|
||||||
import com.rest.api.advice.exception.CEmailSigninFailedException;
|
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.config.security.JwtTokenProvider;
|
||||||
|
import com.rest.api.entity.User;
|
||||||
import com.rest.api.model.response.CommonResult;
|
import com.rest.api.model.response.CommonResult;
|
||||||
import com.rest.api.model.response.SingleResult;
|
import com.rest.api.model.response.SingleResult;
|
||||||
|
import com.rest.api.model.social.KakaoProfile;
|
||||||
import com.rest.api.repo.UserJpaRepo;
|
import com.rest.api.repo.UserJpaRepo;
|
||||||
import com.rest.api.service.ResponseService;
|
import com.rest.api.service.ResponseService;
|
||||||
|
import com.rest.api.service.social.KakaoService;
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
import io.swagger.annotations.ApiParam;
|
import io.swagger.annotations.ApiParam;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@Api(tags = {"1. Sign"})
|
@Api(tags = {"1. Sign"})
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@@ -29,9 +31,10 @@ public class SignController {
|
|||||||
private final JwtTokenProvider jwtTokenProvider;
|
private final JwtTokenProvider jwtTokenProvider;
|
||||||
private final ResponseService responseService;
|
private final ResponseService responseService;
|
||||||
private final PasswordEncoder passwordEncoder;
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
private final KakaoService kakaoService;
|
||||||
|
|
||||||
@ApiOperation(value = "로그인", notes = "이메일 회원 로그인을 한다.")
|
@ApiOperation(value = "로그인", notes = "이메일 회원 로그인을 한다.")
|
||||||
@GetMapping(value = "/signin")
|
@PostMapping(value = "/signin")
|
||||||
public SingleResult<String> signin(@ApiParam(value = "회원ID : 이메일", required = true) @RequestParam String id,
|
public SingleResult<String> signin(@ApiParam(value = "회원ID : 이메일", required = true) @RequestParam String id,
|
||||||
@ApiParam(value = "비밀번호", required = true) @RequestParam String password) {
|
@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()));
|
return responseService.getSingleResult(jwtTokenProvider.createToken(String.valueOf(user.getMsrl()), user.getRoles()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation(value = "소셜 로그인", notes = "소셜 회원 로그인을 한다.")
|
||||||
|
@PostMapping(value = "/signin/{provider}")
|
||||||
|
public SingleResult<String> 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 = "회원가입을 한다.")
|
@ApiOperation(value = "가입", notes = "회원가입을 한다.")
|
||||||
@GetMapping(value = "/signup")
|
@PostMapping(value = "/signup")
|
||||||
public CommonResult signin(@ApiParam(value = "회원ID : 이메일", required = true) @RequestParam String id,
|
public CommonResult signup(@ApiParam(value = "회원ID : 이메일", required = true) @RequestParam String id,
|
||||||
@ApiParam(value = "비밀번호", required = true) @RequestParam String password,
|
@ApiParam(value = "비밀번호", required = true) @RequestParam String password,
|
||||||
@ApiParam(value = "이름", required = true) @RequestParam String name) {
|
@ApiParam(value = "이름", required = true) @RequestParam String name) {
|
||||||
|
|
||||||
@@ -56,4 +70,26 @@ public class SignController {
|
|||||||
.build());
|
.build());
|
||||||
return responseService.getSuccessResult();
|
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> 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,11 +33,11 @@ public class UserController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ApiImplicitParams({
|
@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)로 회원을 조회한다")
|
@ApiOperation(value = "회원 단건 조회", notes = "회원번호(msrl)로 회원을 조회한다")
|
||||||
@GetMapping(value = "/user")
|
@GetMapping(value = "/user")
|
||||||
public SingleResult<User> findUserById(@ApiParam(value = "언어", defaultValue = "ko") @RequestParam String lang) {
|
public SingleResult<User> findUser() {
|
||||||
// SecurityContext에서 인증받은 회원의 정보를 얻어온다.
|
// SecurityContext에서 인증받은 회원의 정보를 얻어온다.
|
||||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
String id = authentication.getName();
|
String id = authentication.getName();
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
package com.rest.api.entity;
|
package com.rest.api.entity;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.*;
|
||||||
import lombok.Builder;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
@@ -25,13 +22,15 @@ public class User implements UserDetails {
|
|||||||
@Id // pk
|
@Id // pk
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
private long msrl;
|
private long msrl;
|
||||||
@Column(nullable = false, unique = true, length = 30)
|
@Column(nullable = false, unique = true, length = 50)
|
||||||
private String uid;
|
private String uid;
|
||||||
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
|
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
|
||||||
@Column(nullable = false, length = 100)
|
@Column(length = 100)
|
||||||
private String password;
|
private String password;
|
||||||
@Column(nullable = false, length = 100)
|
@Column(nullable = false, length = 100)
|
||||||
private String name;
|
private String name;
|
||||||
|
@Column(length = 100)
|
||||||
|
private String provider;
|
||||||
|
|
||||||
@ElementCollection(fetch = FetchType.EAGER)
|
@ElementCollection(fetch = FetchType.EAGER)
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
|
|||||||
22
src/main/java/com/rest/api/model/social/KakaoProfile.java
Normal file
22
src/main/java/com/rest/api/model/social/KakaoProfile.java
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/main/java/com/rest/api/model/social/RetKakaoAuth.java
Normal file
14
src/main/java/com/rest/api/model/social/RetKakaoAuth.java
Normal file
@@ -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;
|
||||||
|
}
|
||||||
@@ -7,4 +7,6 @@ import java.util.Optional;
|
|||||||
public interface UserJpaRepo extends JpaRepository<User, Long> {
|
public interface UserJpaRepo extends JpaRepository<User, Long> {
|
||||||
|
|
||||||
Optional<User> findByUid(String email);
|
Optional<User> findByUid(String email);
|
||||||
|
|
||||||
|
Optional<User> findByUidAndProvider(String uid, String provider);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ public class ResponseService {
|
|||||||
|
|
||||||
// enum으로 api 요청 결과에 대한 code, message를 정의합니다.
|
// enum으로 api 요청 결과에 대한 code, message를 정의합니다.
|
||||||
public enum CommonResponse {
|
public enum CommonResponse {
|
||||||
SUCCESS(0, "성공하였습니디.");
|
SUCCESS(0, "성공하였습니다.");
|
||||||
|
|
||||||
int code;
|
int code;
|
||||||
String msg;
|
String msg;
|
||||||
|
|||||||
70
src/main/java/com/rest/api/service/social/KakaoService.java
Normal file
70
src/main/java/com/rest/api/service/social/KakaoService.java
Normal file
@@ -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<MultiValueMap<String, String>> request = new HttpEntity<>(null, headers);
|
||||||
|
try {
|
||||||
|
// Request profile
|
||||||
|
ResponseEntity<String> 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<String, String> 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<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);
|
||||||
|
ResponseEntity<String> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/main/resources/application-alpha.yml
Normal file
24
src/main/resources/application-alpha.yml
Normal file
@@ -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
|
||||||
20
src/main/resources/application-local.yml
Normal file
20
src/main/resources/application-local.yml
Normal file
@@ -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
|
||||||
@@ -1,17 +1,16 @@
|
|||||||
server:
|
|
||||||
port: 8080
|
|
||||||
|
|
||||||
spring:
|
spring:
|
||||||
datasource:
|
profiles:
|
||||||
url: jdbc:h2:tcp://localhost/~/test
|
active: local # 디폴트 환경
|
||||||
driver-class-name: org.h2.Driver
|
|
||||||
username: sa
|
|
||||||
jpa:
|
|
||||||
database-platform: org.hibernate.dialect.H2Dialect
|
|
||||||
properties.hibernate.hbm2ddl.auto: update
|
|
||||||
showSql: true
|
|
||||||
messages:
|
messages:
|
||||||
basename: i18n/exception
|
basename: i18n/exception
|
||||||
encoding: UTF-8
|
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:
|
jwt:
|
||||||
secret: govlepel@$&
|
secret: govlepel@$&
|
||||||
|
|||||||
@@ -12,4 +12,10 @@ entryPointException:
|
|||||||
msg: "You do not have permission to access this resource."
|
msg: "You do not have permission to access this resource."
|
||||||
accessDenied:
|
accessDenied:
|
||||||
code: "-1003"
|
code: "-1003"
|
||||||
msg: "A resource that can not be accessed with the privileges it has."
|
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."
|
||||||
@@ -12,4 +12,10 @@ entryPointException:
|
|||||||
msg: "해당 리소스에 접근하기 위한 권한이 없습니다."
|
msg: "해당 리소스에 접근하기 위한 권한이 없습니다."
|
||||||
accessDenied:
|
accessDenied:
|
||||||
code: "-1003"
|
code: "-1003"
|
||||||
msg: "보유한 권한으로 접근할수 없는 리소스 입니다."
|
msg: "보유한 권한으로 접근할수 없는 리소스 입니다."
|
||||||
|
communicationError:
|
||||||
|
code: "-1004"
|
||||||
|
msg: "통신 중 오류가 발생하였습니다."
|
||||||
|
existingUser:
|
||||||
|
code: "-1005"
|
||||||
|
msg: "이미 가입한 회원입니다. 로그인을 해주십시오."
|
||||||
6
src/main/resources/templates/social/login.ftl
Normal file
6
src/main/resources/templates/social/login.ftl
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<button onclick="popupKakaoLogin()">KakaoLogin</button>
|
||||||
|
<script>
|
||||||
|
function popupKakaoLogin() {
|
||||||
|
window.open('${loginUrl}', 'popupKakaoLogin', 'width=700,height=500,scrollbars=0,toolbar=0,menubar=no')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
5
src/main/resources/templates/social/redirectKakao.ftl
Normal file
5
src/main/resources/templates/social/redirectKakao.ftl
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
access_token : ${authInfo.access_token}<br>
|
||||||
|
token_type : ${authInfo.token_type}<br>
|
||||||
|
refresh_token : ${authInfo.refresh_token}<br>
|
||||||
|
expires_in : ${authInfo.expires_in}<br>
|
||||||
|
scope : ${authInfo.scope}<br>
|
||||||
@@ -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() {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
137
src/test/java/com/rest/api/controller/v1/SignControllerTest.java
Normal file
137
src/test/java/com/rest/api/controller/v1/SignControllerTest.java
Normal file
@@ -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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
140
src/test/java/com/rest/api/controller/v1/UserControllerTest.java
Normal file
140
src/test/java/com/rest/api/controller/v1/UserControllerTest.java
Normal file
@@ -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<String, String> 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<String, String> 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> 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/test/java/com/rest/api/repo/UserJpaRepoTest.java
Normal file
46
src/test/java/com/rest/api/repo/UserJpaRepoTest.java
Normal file
@@ -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> 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변수 값이 같은지 체크
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user