🍃 Spring/🌱 Spring Boot를 이용한 RESTful Web Service
[Spring Boot를 이용한 RESTful Web Services 개발] 22~24강
락꿈사
2022. 2. 9. 16:41
Response 데이터 제어를 위한 Filtering
개별 사용자 조회
- 사용자 정보 관리 REST API 데이터 중 클라이언트에게 전달해주고자 하는 값 제어하기
- domain 클래스가 가지고 있었던 정보 중 외부에 노출시키고 싶지 않을 경우 사용
- 스프링 부트에서 Filtering이라는 기능 사용
- User 클래스에서 비밀번호, 주민번호 등 중요한 정보 추가
package com.example.restfulwebservice.user;
import lombok.AllArgsConstructor;
import lombok.Data;
import javax.validation.constraints.Past;
import javax.validation.constraints.Size;
import java.util.Date;
@Data
@AllArgsConstructor
public class User {
private Integer id;
@Size(min=2, message = "Name은 2글자 이상 입력해주세요.")
private String name;
@Past
private Date joinDate;
private String password;
private String ssn;
}
- UserDaoService 클래스의 List<User> (DB역할)을 위에 맞게 수정
//데이터베이스에 3개의 데이터가 들어있다고 가정
static {
users.add(new User(1, "roxy", new Date(), "pass1", "701010-1111111"));
users.add(new User(2, "yujin", new Date(), "pass2", "801010-2222222"));
users.add(new User(3, "kyj", new Date(), "pass3", "901010-3333333"));
}
데이터 외부 노출 제어 방법
1. 해당 데이터 보내지 않기
2. 해당 데이터를 Null 값으로 변환하여 반환하기
3. 해당 데이터를 특수한 문자(ex. *)로 변환하여 반환하기
어노테이션을 통한 데이터 노출 제어
- fasterxml의 jackson 라이브러리를 사용하여 외부에 노출되는 데이터를 제어할 수 있음
- User 클래스 수정
package com.example.restfulwebservice.user;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import javax.validation.constraints.Past;
import javax.validation.constraints.Size;
import java.util.Date;
@Data
@AllArgsConstructor
public class User {
private Integer id;
@Size(min=2, message = "Name은 2글자 이상 입력해주세요.")
private String name;
@Past
private Date joinDate;
// @JsonIgnore 어노테이션을 추가하여 이 데이터를 무시
@JsonIgnore
private String password;
@JsonIgnore
private String ssn;
}
- @JsonIgnoreProperties(value={"filed", "filed"}) 로 일괄 처리도 가능
package com.example.restfulwebservice.user;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor;
import lombok.Data;
import javax.validation.constraints.Past;
import javax.validation.constraints.Size;
import java.util.Date;
@Data
@AllArgsConstructor
@JsonIgnoreProperties(value={"password", "ssn"}) // 일괄 처리
public class User {
private Integer id;
@Size(min=2, message = "Name은 2글자 이상 입력해주세요.")
private String name;
@Past
private Date joinDate;
private String password;
private String ssn;
}
내부 프로그래밍을 통한 데이터 노출 제어
- 단순하게 @JsonIgnore, @JsonIgnoreProperties를 통한 제어보다 프로그램 내부에서 여러 필드를 제어하고, 필터로 선언되어 있는 다양한 클래스를 사용할 수 있음 (ex. User클래스 뿐 아니라 AdminUser, VIPUser 등 여러 DTO를 만들고 해당 DTO에 맞는 필터를 추가하면 컨트롤러에서 필요한 값을 불러와서 사용할 수 있음)
- @JsonFilter("filterName") 어노테이션을 사용하여 필터 생성
package com.example.restfulwebservice.user;
import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor;
import lombok.Data;
import javax.validation.constraints.Past;
import javax.validation.constraints.Size;
import java.util.Date;
@Data
@AllArgsConstructor
@JsonFilter("UserInfo") // 괄호 안의 값은 임의로 지정하면 되며, 이 값은 Controller 클래스나 Service 클래스에서 사용됨
public class User {
private Integer id;
@Size(min=2, message = "Name은 2글자 이상 입력해주세요.")
private String name;
@Past
private Date joinDate;
private String password;
private String ssn;
}
- 기존의 UserController를 복사하여 AdminUserController 클래스 생성 (관리자만을 위한 클리티컬한 기능을 사용할 수 있는 컨트롤러)
- createUser, deleteUser 메소드 삭제
- @RequestMapping("URI") 어노테이션을 클래스에 선언하여 메소드 내의 URI 값에 "/admin/~"이 공통적으로 붙도록 수정
- 사용자 정보를 상세 조회하는 retrieveUser 메소드에 SimpleBeanPropertyFilter 인스턴스를 사용하여 Bean이 properties를 제어할 수 있도록 함
package com.example.restfulwebservice.user;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import javax.validation.Valid;
import java.net.URI;
import java.util.List;
import java.util.Map;
@RestController
// 이 클래스 내의 모든 메소드의 URI 값 앞에 "/admin" 이 공통적으로 붙도록 설정
@RequestMapping("/admin")
public class AdminUserController {
private UserDaoService service;
public AdminUserController(UserDaoService service){
this.service = service;
}
@GetMapping("/users")
public List<User> retrieveAllUsers(){
return service.findAll(); //service의 전체 사용자 목록 조회 메소드 반환
}
@GetMapping("/users/{id}")
// filter가 적용될 수 있는 타입인 MappingJacksonValue 타입으로 반환
public MappingJacksonValue retrieveUser(@PathVariable int id){
User user = service.findOne(id);
if (user == null){
throw new UserNotFoundException(String.format("ID[%s] not found", id));
}
// Bean이 property를 제어할 수 있도록 하는 인스턴스
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name", "joinDate", "ssn"); // 포함시키고자 하는 필터 선언. "id", "name", "joinDate", "ssn" 전달
// SimpleBeanPropertyFilter 인스턴스를 사용할 수 있는 형태로 변경
// addFilter()를 사용해서 filter 추가
// filter를 추가할 때는 어떠한 Bean을 대상으로 추가된 필터인지 Bean의 이름을 써줘야 하는데, User 클래스에 @JsonFilter("name") 어노테이션의 "name"값을 첫번째 인자로 넣어줌
FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo", filter);
// MappingJacksonValue 객체에 user 전달
MappingJacksonValue mapping = new MappingJacksonValue(user);
// mapping에 필터 적용
mapping.setFilters(filters);
return mapping;
}
}
전체 사용자 조회
- 사용자 정보를 상세 조회하는 retrieveAllUsers 메소드에 위에서 했던 필터 연결하는 코드 추가
package com.example.restfulwebservice.user;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import javax.validation.Valid;
import java.net.URI;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/admin")
public class AdminUserController {
private UserDaoService service;
public AdminUserController(UserDaoService service){
this.service = service;
}
//전체 사용자 목록을 조회하는 메소드 등록
//endpoint로 /users를 호출했을 경우 이 메소드가 실행됨
@GetMapping("/users")
public MappingJacksonValue retrieveAllUsers(){
// 기존의 service.findAll()이 리턴하는 값을 전체 반환하던 것에서 리스트 변수를 선언하여 분리(command+alt+v)
List<User> users = service.findAll();
// Bean이 property를 제어할 수 있도록 하는 인스턴스
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name", "joinDate", "password"); // 포함시키고자 하는 필터 선언. "id", "name", "joinDate", "password" 전달
// SimpleBeanPropertyFilter 인스턴스를 사용할 수 있는 형태로 변경
// addFilter()를 사용해서 filter 추가
// filter를 추가할 때는 어떠한 Bean을 대상으로 추가된 필터인지 Bean의 이름을 써줘야 하는데, User 클래스에 @JsonFilter("name") 어노테이션의 "name"값을 첫번째 인자로 넣어줌
FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo", filter);
// MappingJacksonValue 객체에 List형태로 받아온 users 전달
MappingJacksonValue mapping = new MappingJacksonValue(users);
// mapping에 필터 적용
mapping.setFilters(filters);
return mapping; //service의 전체 사용자 목록 조회 메소드 반환
}
@GetMapping("/users/{id}")
public MappingJacksonValue retrieveUser(@PathVariable int id){ //path variable을 사용하고 있기 때문에 @PathVariable 사용
User user = service.findOne(id);
if (user == null){
throw new UserNotFoundException(String.format("ID[%s] not found", id));
}
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name", "password", "ssn");
FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo", filter);
MappingJacksonValue mapping = new MappingJacksonValue(user);
mapping.setFilters(filters);
return mapping;
}
}