Validation / JWT

이동언·2024년 10월 15일

new world

목록 보기
54/62
post-thumbnail

10.15

1.validation

👉 예외처리와 같은 개념으로 사용된다.

1-1. CategoryDTO

@Data
public class CategoryDTO {

    private Long cno;

    @NotNull // 유효성 검증에서 에러가 발생
    private String cname;
}

1-2. CategoryController

@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로 변환된다.

2. customError

2-1. PageRequestDTO

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에 해당되지않는 내용을 넣는다면 에러가 발생한다.




2-2. ControllerAdivce


@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에서 발생하는 에러이다.

2-3. CustomSecurityConfig

@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에 접근은 인증없이 모든 사용자가 사용가능한것이다.




3. JWT

3-1. SecurityConfig(JWT를 사용하기 위한 login disable)


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




3-2. JWTCheckFilter


@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에 사용자 정의필터를 추가해준다.




3-3. MemberController

@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이 생성되는 순서

3-4. jwtCheckFilter

@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에 접근하게 될땐 프리패스




3-5. MemberService


@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의 형태로 객체가 만들어진다.




3-6. MemberController


@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등 오류코드 반환이 가능하다.

0개의 댓글