TIL

[231120] Spring 숙련주차 개인과제 해설

진진리 2023. 11. 20. 20:47
728x90
Authentication Filter 대신 로그인 관련 메소드를 사용하는 방법

 

숙련과제를 할 때 나는 기존 강의에서 사용했었던 Spring security에서 기본 제공하는 로그인 기능을 사용하였다.

JwtAuthenticationFilter는 HttpServletRequest로 들어온 정보를 바탕으로 Authentication 객체를 성공적으로 생성했을 때 jwt 토큰을 헤더에 담아 반환하는 역할을 수행한다.

 

나는 로그인 기능을 구현하려고 했을 때 처음부터 이 필터를 사용하여 구현하는 것만 생각했는데

올라온 해설 강의 영상을 보니 Authentication 필터를 구현하지 않고 메소드로 구현하는 방법도 있어서 정리해보려고 한다.

 

1. WebSecurityConfig에 AuthorizationFilter만 등록한다.

    @Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		// CSRF 설정
		http.csrf((csrf) -> csrf.disable());

		// 기본 설정인 Session 방식은 사용하지 않고 JWT 방식을 사용하기 위한 설정
		http.sessionManagement((sessionManagement) ->
			sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
		);

		http.authorizeHttpRequests((authorizeHttpRequests) ->
			authorizeHttpRequests
				.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() // resources 접근 허용 설정
				.requestMatchers("/api/users/**").permitAll() // '/api/users/'로 시작하는 요청 모두 접근 허가
				.anyRequest().authenticated() // 그 외 모든 요청 인증처리
		);

		// 필터 관리
		http.addFilterBefore(jwtAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class);

		return http.build();
	}

 

 

2. UserController에서 로그인 Post 요청을 받는다.

  • UserService에서 발생한 예외 처리를 수행한다.
  • JwtUtil을 이용해 만든 jwt 토큰을 헤더에 넣어 반환한다.
    @PostMapping("/login")
	public ResponseEntity<CommonResponseDto> login(@RequestBody UserRequestDto userRequestDto, HttpServletResponse response) {
		try {
			userService.login(userRequestDto);
		} catch (IllegalArgumentException e) {
			return ResponseEntity.badRequest().body(new CommonResponseDto(e.getMessage(), HttpStatus.BAD_REQUEST.value()));
		}

		response.setHeader(JwtUtil.AUTHORIZATION_HEADER, jwtUtil.createToken(userRequestDto.getUsername()));

		return ResponseEntity.ok().body(new CommonResponseDto("로그인 성공", HttpStatus.OK.value()));
	}

 

 

3. UserService에서 로그인 기능을 수행한다.

    public void login(UserRequestDto userRequestDto) {
		String username = userRequestDto.getUsername();
		String password = userRequestDto.getPassword();

		User user = userRepository.findByUsername(username)
			.orElseThrow(() -> new IllegalArgumentException("등록된 유저가 없습니다."));

		if(!passwordEncoder.matches(password, user.getPassword())) {
			throw new IllegalArgumentException("비밀번호가 일치하지 않습니다.");
		}
	}

 

 

예외 처리 응답과 정상 응답이 다를 때의 반환 타입을 설정하는 법

 

모범 답안에서는 주로 Controller에서 try-catch문으로 예외처리를 하였는데

이때 정상적으로 동작했을 때 반환하는 responseDto와 상태 메시지와 http 상태 번호를 반환하는 commonResponseDto가 있을 때 반환 타입을 설정하는 방법이 인상깊었다.

 

반환 타입을 CommonResponseDto로 설정한 뒤 ResponseDto에서 해당 클래스를 상속받도록 extends해주었다.

    @GetMapping("/{todoId}")
	public ResponseEntity<CommonResponseDto> getTodo(@PathVariable Long todoId) {
		try {
			TodoResponseDTO responseDTO = todoService.getTodoDto(todoId);
			return ResponseEntity.ok().body(responseDTO);
		} catch (IllegalArgumentException e) {
			return ResponseEntity.badRequest().body(new CommonResponseDto(e.getMessage(), HttpStatus.BAD_REQUEST.value()));
		}
	}
@Setter
@Getter
public class TodoResponseDTO extends CommonResponseDto {
	private Long id;
	private String title;
	private String content;
	private Boolean isCompleted;
	private UserDTO user;
	private LocalDateTime createDate;

	public TodoResponseDTO(Todo todo) {
		this.id = todo.getId();
		this.title = todo.getTitle();
		this.content = todo.getContent();
		this.isCompleted = todo.getIsCompleted();
		this.user = new UserDTO(todo.getUser());
		this.createDate = todo.getCreateDate();
	}
}

 

 

예외처리: try-catch와 ExceptionHandler의 차이

 

나는 과제를 수행할 때 controller에서 ExceptionHandler를 이용해 예외처리를 하였는데 모범 과제에서는 try-catch문을 이용하였다.

그래서 둘의 차이가 궁금해져서 정리해보려고 한다.

 

  • try-catch문
    • catch 블럭에서 매개변수로 어떤 예외를 catch할지 명시
    • 가독성이 좋지 않고 예외 종류가 많아짐에 따라 catch 블럭이 많이지게 됨
  • ExceptionHandler
    • @Controller와 @RestController가 붙은 클래스에서 발생한 예외를 잡아 메서드 한곳에서 처리할 수 있도록 하는 기능을 수행
    • 서비스 레이어에서 발생한 예외를 처리할 수 있는 이유: 컨트롤러에서 서비스의 메서드를 호출하고 예외가 발생하였으므로 함수 호출 스택의 가장 밑에 쌓여있는 것은 컨트롤러 레이어이기 때문에
    • 모든 컨트롤러마다 @ExceptionHandler를 사용하여 여러 메서드를 작성해야 함
  • ControllerAdvice & RestControllerAdvice
    • 모든 컨트롤러에서 발생할 수 있는 예외를 한곳에서 처리 가능
    • GlobalExceptionHandler 클래스 내부에 @ExceptionHandler를 사용하여 예외처리 메소드를 작성
    • 예시
@RestControllerAdvice
    public class GlobalExceptionHandler {
	
    @ExceptionHandler(NullPointerException.class)
    public String NPE() {
		return "목록이 존재하지 않습니다";
    }

    @ExceptionHandler(DuplicateFriendException.class)
    public ResponseEntity<ErrorResponse> DuplicateFriend (DuplicateFriendException e) {
	     return ErrorResponse.toResponse(e.getErrorCode()); 
    }	

    @ExceptionHandler(NoUserException.class)
    public ResponseEntity<ErrorResponse> NoUser(NoUserException e) {
	    return ErrorResponse.toResponse(e.getErrorCode());
    }

}

 


참고:

https://velog.io/@kekim20/Spring-boot%EC%98%88%EC%99%B8%EC%B2%98%EB%A6%AC-2-try-catch%EC%99%80-ExceptionHandler-ControllerAdvice