
👉 예외처리와 같은 개념으로 사용된다.
@Data
public class CategoryDTO {
private Long cno;
@NotNull // 유효성 검증에서 에러가 발생
private String cname;
}
@RestController
@Log4j2
@RequiredArgsConstructor
@RequestMapping("/api/v1/category")
public class CategoryController {
@PostMapping(value = "", produces = MediaType.APPLICATION_JSON_VALUE) // produces는 기본 json형태
public ResponseEntity<Map<String, Long>> register( // map을 사용하여 고정된 키값을 사용할수있으므로
@RequestBody @Validated CategoryDTO categoryDTO //
) {
log.info("categoryDTO", categoryDTO);
return null;
}
}
👉 controller에서 validated를 사용하여 dto에서 사용한 Notnull의 유효성검증을 사용할수있음.
👉 requestBody를 사용하여 json으로 입력된 데이터가 categorydto로 변환된다.
package com.example.demo.common;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import lombok.*;
import lombok.experimental.SuperBuilder;
@Data
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
public class PageRequestDTO {
@Builder.Default
@Min(value=1, message = "over 1")
private int page = 1;
@Max(value=100, message = "cannot over 100")
@Builder.Default
private int size = 10;
}
👉 controller에서 post로 전달된 데이터가 DTO의 유효성검사에 해당된다.
👉 만약 postMan에서 min과 max에 해당되지않는 내용을 넣는다면 에러가 발생한다.

@RestControllerAdvice
@Log4j2
public class CategoryControllerAdvice { // dto에서 발생한 오류가 controllerAdvice로 전달
@ExceptionHandler(MethodArgumentNotValidException.class) // 메서드의 인자로 전달된 객체가 유효성 검증을 실패
public ResponseEntity<Map<String, Object>> handle(MethodArgumentNotValidException ex) {
BindingResult bindingResult = ex.getBindingResult();
Map<String, Object> map = new HashMap<>();
ex.getFieldErrors().forEach(fieldError -> {
log.error("advice-------------");
String key = fieldError.getField();
Object value = fieldError.getDefaultMessage();
map.put(key,value);
});
return ResponseEntity.status(400).body(map);
}
}
👉 유효성검증에 해당되어 advice의 handle메소드가 실행되어 오류정보를 추출하여 map에 저장되어 return된다.
👉 해당에러는 쿼리스트링과 같은 spring에서 발생하는 에러이다.
@Configuration
public class CustomSecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(
(auth) -> auth.requestMatchers("/api/v1/product/list").permitAll()
);
http.csrf(config -> config.disable());
return http.build();
}
}
👉 해당코드는 product/list에 접근은 인증없이 모든 사용자가 사용가능한것이다.
@Configuration
public class CustomSecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.formLogin(config -> config.disable()); // login화면 제공하지않는다.
http.sessionManagement(config -> config.sessionCreationPolicy(SessionCreationPolicy.NEVER)); // login에 대한 세션 제공하지 않는다.
http.csrf(config -> config.disable()); // csrf 제공하지않는다.
return http.build();
}
}
@Log4j2
public class JWTCheckFilter extends OncePerRequestFilter {
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
log.info("shouldNotFilter");
return false;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
log.info("doFilterInternal");
filterChain.doFilter(request, response);
}
}
👉 해당코드는 jwtToken을 검사하는 사용자 정의 필터인데, shouldNotFilter는 필터에 걸러지지않고 승인되는 코드이고, doFilterInternal메소드는 필터에 걸려 인증이 필요한 메소드이다.
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.formLogin(config -> config.disable()); // login화면 제공하지않는다.
http.sessionManagement(config -> config.sessionCreationPolicy(SessionCreationPolicy.NEVER)); // login에 대한 세션 제공하지 않는다.
http.csrf(config -> config.disable()); // csrf 제공하지않는다.
http.addFilterBefore(new JWTCheckFilter(), UsernamePasswordAuthenticationFilter.class); //SpringSecurity에 사용자 정의필터 추가
return http.build();
}
👉 jwtfilter와 같은 사용자정의 필터를 생성한후에 securityConfig에 사용자 정의필터를 추가해준다.
@RestController
@RequestMapping("/api/v1/member")
@Log4j2
public class MemberController {
@PostMapping("/makeToken")
public ResponseEntity<TokenResponseDTO> makeToken(@RequestBody TokenRequestDTO tokenRequestDTO) {
log.info("makeToken");
log.info("--------------");
return null;
}
}
👉 memberController는 ap1/v1/member/makeToken - 토큰발행
-> 해당 post를 실행하게 된다면 jwtCheckFilter에서 shouldNotFilter에 포함되어있으므로 통과되어 memberController에 전달되어 jwtToken이 생성되는 순서
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
log.info("shouldNotFilter");
String uri = request.getRequestURI();
if(uri.equals("/api/v1/member/makeToken")){ // 호출한 경로가 이곳일때 필터링하지 않는다. 여기는 토큰을 받아야하는곳이므로
return true;
}
return false;
}
👉 memberController는 filter에 걸리지않고 필터링하지 않고 토큰을 생성하는곳 이므로 해당 url에 접근하게 될땐 프리패스
@Service
@Transactional
@Log4j2
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
private final PasswordEncoder passwordEncoder;
public MemberDTO authenticate(String email, String password) {
Optional<MemberEntity> result = memberRepository.findById(email); //email이 있으면 result는 참값
MemberEntity member = result.orElseThrow(() -> CommonExceptions.READ_ERROR.getTaskException()); // result가 없다면 예외발생
String enPw = member.getPw(); // 인코딩된 패스워드
boolean match = passwordEncoder.matches(password, enPw); // 첫번째 매개변수는 내가 입력한 패스워드 두번째는 인코딩 된 패스워드
if(! match) { // 입력한 비밀번호와 인코딩된 비밀번호가 일치하지않으면 만들어놨던 예외로 전달
throw CommonExceptions.READ_ERROR.getTaskException();
}
MemberDTO memberDTO = new MemberDTO();
memberDTO.setEmail(email);
memberDTO.setPw(enPw);
memberDTO.setRole(member.getRole().toString());
return memberDTO;
}
}
👉 해당 서비스에서는 email password를 사용하여 예외처리를 하는데, email이 없으면 예외처리되고, 인코딩된 패스워드가 일치하지 않으면 예외처리되고 그렇게 살아남은 email과 pw는 memberDTO의 형태로 객체가 만들어진다.
@RestController
@RequestMapping("/api/v1/member")
@Log4j2
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
private final JWTUtil jwtUtil;
@PostMapping("/makeToken")
public ResponseEntity<TokenResponseDTO> makeToken(@RequestBody TokenRequestDTO tokenRequestDTO) {
log.info("makeToken");
log.info("--------------");
MemberDTO memberDTO = memberService.authenticate(
tokenRequestDTO.getEmail(),
tokenRequestDTO.getPw());
Map<String, Object> cliaimMap = Map.of(
"email",memberDTO.getEmail(),
"role",memberDTO.getRole());
String accesToken = jwtUtil.createToken(cliaimMap, 5);
String refreshToken = jwtUtil.createToken(cliaimMap, 360);
TokenResponseDTO tokenResponseDTO = new TokenResponseDTO();
tokenResponseDTO.setAcessToken(accesToken);
tokenResponseDTO.setRefreshToken(refreshToken);
tokenResponseDTO.setEmail(memberDTO.getEmail());
return ResponseEntity.ok(tokenResponseDTO);
}
}
👉 service에서 뽑은 dto형태의 email, pw를 가져와서 map인 키값형태로 만들고 5분인 access토큰과 360분인 refresh토큰을 생성하여 tokenResponseDTO의 형태로 ResponseEntity로 리턴한다.
👉 ResponseEntity의 return값을 통해서 200,400,500등 오류코드 반환이 가능하다.