Merge branch 'master' into feature/readme.md

This commit is contained in:
codej99
2019-04-18 15:25:07 +09:00
committed by GitHub
15 changed files with 328 additions and 44 deletions

View File

@@ -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'
@@ -32,5 +33,6 @@ dependencies {
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'
}

View File

@@ -51,7 +51,7 @@ 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"));
}

View File

@@ -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);
}
}

View File

@@ -1,8 +1,6 @@
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;
@@ -17,12 +15,10 @@ 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,
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception) throws IOException,
ServletException {
RequestDispatcher dispatcher = request.getRequestDispatcher("/exception/accessdenied");
dispatcher.forward(request, response);
}
RequestDispatcher dispatcher = request.getRequestDispatcher("/exception/accessdenied");
dispatcher.forward(request, response);
}
}

View File

@@ -33,7 +33,6 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
.authorizeRequests() // 다음 리퀘스트에 대한 사용권한 체크
.antMatchers("/*/signin", "/*/signup").permitAll() // 가입 및 인증 주소는 누구나 접근가능
.antMatchers(HttpMethod.GET, "/helloworld/**").permitAll() // hellowworld로 시작하는 GET요청 리소스는 누구나 접근가능
.antMatchers("/*/users").hasRole("ADMIN")
.anyRequest().hasRole("USER") // 그외 나머지 요청은 모두 인증된 회원만 접근 가능
.and()
.exceptionHandling().accessDeniedHandler(new CustomAccessDeniedHandler())

View File

@@ -9,6 +9,8 @@ import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HelloController {
private static final String HELLO = "helloworld";
@Setter
@Getter
public static class Hello {
@@ -18,19 +20,19 @@ public class HelloController {
@GetMapping(value = "/helloworld/string")
@ResponseBody
public String helloworldString() {
return "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;
}
}

View File

@@ -12,10 +12,7 @@ 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;
@@ -31,7 +28,7 @@ public class SignController {
private final PasswordEncoder passwordEncoder;
@ApiOperation(value = "로그인", notes = "이메일 회원 로그인을 한다.")
@GetMapping(value = "/signin")
@PostMapping(value = "/signin")
public SingleResult<String> signin(@ApiParam(value = "회원ID : 이메일", required = true) @RequestParam String id,
@ApiParam(value = "비밀번호", required = true) @RequestParam String password) {
@@ -43,8 +40,8 @@ public class SignController {
}
@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) {

View File

@@ -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<User> findUserById(@ApiParam(value = "언어", defaultValue = "ko") @RequestParam String lang) {
public SingleResult<User> findUser() {
// SecurityContext에서 인증받은 회원의 정보를 얻어온다.
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String id = authentication.getName();

View File

@@ -25,7 +25,7 @@ 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)

View File

@@ -12,7 +12,7 @@ public class ResponseService {
// enum으로 api 요청 결과에 대한 code, message를 정의합니다.
public enum CommonResponse {
SUCCESS(0, "성공하였습니.");
SUCCESS(0, "성공하였습니.");
int code;
String msg;

View File

@@ -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() {
}
}

View File

@@ -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"));
}
}

View File

@@ -0,0 +1,89 @@
package com.rest.api.controller.v1;
import org.junit.Before;
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.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.context.WebApplicationContext;
import java.time.LocalDateTime;
import java.time.ZoneId;
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;
@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));
}
}

View File

@@ -0,0 +1,119 @@
package com.rest.api.controller.v1;
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.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 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;
private String token;
@Before
public void setUp() throws Exception {
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();
}
@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 {
mockMvc.perform(MockMvcRequestBuilders
.delete("/v1/user/2")
.header("X-AUTH-TOKEN", token))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.success").value(true));
}
}

View 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변수 값이 같은지 체크
}
}