반응형
*** 함께 보면 좋은 글
스프링부트 핵심 가이드(장정우 지음) - 스프링 시큐리티(Spring Security)와 JWT 1편
*책 내용과 다르게, 다음과 같은 환경에서 프로젝트 생성 Windows11(윈도우 11) 환경자바 JDK 17 버전 설치 https://yungenie.tistory.com/11 [Java] 차근차근 Java 설치하기 (JDK17, Window 11)자바 개발 도구 설치 방법
keep-programming-study.tistory.com
2. 스프링 시큐리티와 JWT 적용
6) 커스텀 AccessDeniedHandler, AuthenticationEntryPoint 구현
(1) CustomAccessDeniedHandler
package com.example.demo.config.security;
import java.io.IOException;
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;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* Spring Security에서 접근 권한이 없는 요청(AccessDeniedException 발생 시)을 처리하는 핸들러 클래스
*
* 주요 역할:
* 1. 사용자가 인증은 되었지만, 특정 리소스에 접근할 권한이 없을 때 실행됨
* - 예: ROLE_USER 권한만 있는 사용자가 ROLE_ADMIN 전용 API에 접근
* 2. AccessDeniedHandler 인터페이스 구현 → handle() 메서드 오버라이드
* 3. 로그 기록: 접근 거부 상황을 로깅
* 4. response.sendRedirect("/지정된 경로/") 호출: 접근 거부 시 해당 경로로 리다이렉트
*
* 특징:
* - @Component로 등록되어 Spring Security 설정에서 자동으로 사용 가능
* - "인증은 되었지만 권한이 부족한 경우"에만 동작
*/
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
// Logger 인스턴스 생성
// - SLF4J LoggerFactory를 사용하여 현재 클래스(CustomAccessDeniedHandler)에 대한 로깅 객체를 생성
// - 접근 거부 상황 발생 시 로그를 남겨서 추적 및 디버깅에 활용 가능
private final Logger LOGGER = LoggerFactory.getLogger(CustomAccessDeniedHandler.class);
@Override
public void handle(
HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException exception) throws IOException {
// 접근 거부 상황 발생 시 로그 기록
// - 사용자가 인증은 되었지만 권한이 부족하여 요청이 차단된 경우 실행됨
LOGGER.info("[handle] 접근이 막혔을 때 경로 리다이렉트");
// 접근 거부 시 지정된 경로로 리다이렉트
// - "/sign-api/exception" 경로로 이동시켜 사용자에게 권한 부족 안내 페이지를 제공할 수 있음
// - response.sendRedirect()는 클라이언트 브라우저가 해당 URL로 다시 요청하도록 지시하는 방식
response.sendRedirect("/sign-api/exception");
}
}
(2) CustomAuthenticationEntryPoint
package com.example.demo.config.security;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* 인증 실패(401 Unauthorized) 상황을 처리하는 커스텀 EntryPoint 클래스
*
* 주요 역할:
* - Spring Security에서 인증되지 않은 사용자가 보호된 리소스에 접근할 때 실행됨
* - AuthenticationEntryPoint 인터페이스 구현 → commence() 메서드 오버라이드
* - 로그 기록: 인증 실패 상황을 로깅
* - 클라이언트에게 JSON 형태의 에러 응답 반환
*
* 특징:
* - @Component로 등록되어 Spring Security 설정에서 자동으로 사용 가능
* - "인증 자체가 안 된 경우"에만 동작
*/
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
// Logger 인스턴스 생성
// - SLF4J LoggerFactory를 사용하여 현재 클래스(CustomAuthenticationEntryPoint)에 대한 로깅 객체를 생성
// - 접근 거부 상황 발생 시 로그를 남겨서 추적 및 디버깅에 활용 가능
private final Logger LOGGER = LoggerFactory.getLogger(CustomAuthenticationEntryPoint.class);
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
// ObjectMapper 생성
// - Java 객체를 JSON 문자열로 변환하기 위해 사용
ObjectMapper objectMapper = new ObjectMapper();
// 인증 실패 로그 기록
// - 인증되지 않은 사용자가 보호된 리소스에 접근하려 할 때 실행됨
LOGGER.info("[commence] 인증 실패로 response.sendError 발생");
// 사용자에게 반환할 에러 응답 객체 생성
// - EntryPointErrorResponse는 커스텀 응답 DTO로, 메시지를 담아 클라이언트에 전달
EntryPointErrorResponse entryPointErrorResponse = new EntryPointErrorResponse();
entryPointErrorResponse.setMsg("인증에 실패하였습니다.");
// HTTP 응답 설정
// - 상태 코드: 401 Unauthorized (인증 실패)
// - Content-Type: application/json (JSON 응답)
// - CharacterEncoding: UTF-8 (한글 깨짐 방지)
// response.sendError(response.SC_UNAUTHORIZED)와 같은 형식으로 인증 실패 코드만 전달할수도 있음
response.setStatus(401);
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
// 응답 본문에 JSON 문자열 작성
// - objectMapper.writeValueAsString(entryPointErrorResponse)로 DTO를 JSON으로 변환
response.getWriter().write(objectMapper.writeValueAsString(entryPointErrorResponse));
}
}
(3) EntryPointErrorResponse
package com.example.demo.config.security;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* 인증 실패 시 클라이언트에게 반환할 에러 응답 객체 (DTO)
*
* 주요 역할:
* - CustomAuthenticationEntryPoint에서 사용됨
* - 인증 실패(401 Unauthorized) 상황에서 JSON 응답으로 내려줄 데이터 구조 정의
* - 단순히 메시지(msg) 필드 하나만 포함하여, 클라이언트가 쉽게 에러 내용을 확인할 수 있도록 함
*
* 특징:
* - Lombok 어노테이션(@Data, @NoArgsConstructor, @AllArgsConstructor, @ToString)으로
* getter/setter, 생성자, toString 메서드를 자동 생성
* - 직렬화 시 JSON 형태로 변환되어 클라이언트에 전달됨
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class EntryPointErrorResponse {
/**
* 에러 메시지를 담는 필드
* - 인증 실패 시 "인증에 실패하였습니다." 등의 안내 문구를 저장
* - ObjectMapper를 통해 JSON 변환 시 {"msg":"인증에 실패하였습니다."} 형태로 응답됨
*/
private String msg;
}
7) 회원가입과 로그인 구현
- User 객체 생성을 위한 회원가입 구현
- 생성된 User 객체로 인증을 시도하는 로그인 구현
- 회원가입과 로그인의 도메인은 Sign으로 통합해서 표현하며, 각각 Sign-up과 Sign-in으로 구분하여 기능 구현
(1) SignUpResultDto, SignInResultDto
package com.example.demo.jpa.data.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* 회원가입 결과를 클라이언트에게 전달하기 위한 응답 DTO 클래스
*
* 주요 역할:
* - 회원가입 처리 후 성공/실패 여부, 상태 코드, 메시지를 담아 반환
* - Controller → Service → 클라이언트로 전달되는 데이터 구조 정의
* - JSON 직렬화 시 {"success":true, "code":200, "msg":"회원가입 성공"} 형태로 응답 가능
*
* 특징:
* - Lombok 어노테이션(@Data, @NoArgsConstructor, @AllArgsConstructor, @ToString) 사용
* → getter/setter, 기본 생성자, 전체 필드 생성자, toString 자동 생성
* - 불필요한 보일러플레이트 코드 제거로 가독성 향상
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class SignUpResultDto {
/**
* 회원가입 성공 여부
* - true: 회원가입 성공
* - false: 회원가입 실패
*/
private boolean success;
/**
* 결과 코드
* - 예: 200 (성공), 400 (잘못된 요청), 500 (서버 오류) 등
* - 클라이언트가 결과를 상태 코드 기반으로 처리할 수 있도록 제공
*/
private int code;
/**
* 결과 메시지
* - 회원가입 성공/실패에 대한 설명 문구
* - 예: "회원가입 성공", "이미 존재하는 사용자입니다."
*/
private String msg;
}
package com.example.demo.jpa.data.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* 로그인 결과를 클라이언트에게 전달하기 위한 응답 DTO 클래스
*
* 주요 역할:
* - 로그인 처리 후 JWT 토큰을 포함한 결과를 반환
* - 부모 클래스(SignUpResultDto)의 필드(success, code, msg)를 상속받아 기본 응답 정보 제공
* - 추가적으로 token 필드를 포함하여 로그인 성공 시 발급된 JWT 토큰을 클라이언트에 전달
*
* 특징:
* - Lombok 어노테이션(@Data, @NoArgsConstructor, @AllArgsConstructor, @ToString) 사용
* → getter/setter, 생성자, toString 자동 생성
* - @Builder를 통해 빌더 패턴으로 객체 생성 가능
* - 상속 구조 덕분에 회원가입/로그인 응답 DTO가 일관된 형태를 유지
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class SignInResultDto extends SignUpResultDto {
/**
* 로그인 성공 시 발급되는 JWT 토큰
* - 클라이언트가 이후 요청 시 인증 헤더에 포함하여 사용
*/
private String token;
/**
* 빌더 패턴을 활용한 생성자
* - 부모 클래스(SignUpResultDto)의 생성자를 호출하여 success, code, msg 초기화
* - 현재 클래스의 token 필드도 함께 초기화
*
* @param success 로그인 성공 여부
* @param code 결과 코드 (예: 200 성공, 401 인증 실패 등)
* @param msg 결과 메시지
* @param token JWT 토큰
*/
@Builder
public SignInResultDto(boolean success, int code, String msg, String token) {
super(success, code, msg); // 부모 클래스 생성자 호출
this.token = token; // 현재 클래스 필드 초기화
}
}
(2) SignService 인터페이스
package com.example.demo.service;
import com.example.demo.jpa.data.dto.SignInResultDto;
import com.example.demo.jpa.data.dto.SignUpResultDto;
/**
* 회원가입 및 로그인 관련 기능을 정의하는 서비스 인터페이스
*
* 주요 역할:
* - 회원가입(signUp)과 로그인(signIn) 기능을 추상화하여 구현 클래스에서 실제 로직을 작성하도록 강제
* - Controller 계층에서 이 인터페이스를 호출하여 서비스 로직을 실행
* - DTO(SignUpResultDto, SignInResultDto)를 반환하여 클라이언트에 결과 전달
*
* 특징:
* - 인터페이스로 정의되어 있어 다양한 구현체를 만들 수 있음 (예: DB 기반, 외부 인증 서버 기반 등)
* - 예외 처리나 세부 로직은 구현 클래스에서 담당
*/
public interface SignService {
/**
* 회원가입 기능
*
* @param id 사용자 ID
* @param password 사용자 비밀번호
* @param name 사용자 이름
* @param role 사용자 권한 (예: ROLE_USER, ROLE_ADMIN)
* @return SignUpResultDto 회원가입 결과 DTO
* - success: 성공 여부
* - code: 상태 코드
* - msg: 결과 메시지
*/
SignUpResultDto signUp(String id, String password, String name, String role);
/**
* 로그인 기능
*
* @param id 사용자 ID
* @param password 사용자 비밀번호
* @return SignInResultDto 로그인 결과 DTO
* - success, code, msg: 기본 응답 정보
* - token: 로그인 성공 시 발급된 JWT 토큰
* @throws RuntimeException 로그인 실패 시 예외 발생
*/
SignInResultDto signIn(String id, String password) throws RuntimeException;
}
(3) PasswordEncoderConfiguration 클래스
package com.example.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
비밀번호를 안전하게 암호화하고 검증하기 위해
Spring Security에서 제공하는 PasswordEncoder를 Bean으로 등록
**/
@Configuration // 설정 클래스임을 명시
public class PasswordEncoderConfiguration {
@Bean // PasswordEncoder를 Bean으로 등록 → 스프링 컨테이너에서 관리
public PasswordEncoder passwordEncoder() {
// DelegatingPasswordEncoder 생성 (기본은 BCrypt)
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
(4) CommonResponse
package com.example.demo.config.security.common;
/**
* 애플리케이션에서 공통적으로 사용하는 응답 상태 Enum
*
* 주요 역할:
* - API 응답이나 서비스 로직에서 성공/실패 여부를 일관되게 표현하기 위해 사용
* - 각 상태는 코드(int)와 메시지(String)를 함께 제공
* → 클라이언트가 응답을 처리할 때 코드와 메시지를 함께 활용 가능
*
* 특징:
* - SUCCESS: 성공 상태 (code=0, msg="Success")
* - FAIL: 실패 상태 (code=-1, msg="Fail")
* - Enum으로 정의되어 있어 타입 안정성을 보장하고, 상수 값 관리가 용이함
*/
public enum CommonResponse {
SUCCESS(0, "Success"), // 성공 응답
FAIL(-1, "Fail"); // 실패 응답
// 응답 코드 (성공/실패를 숫자로 표현)
int code;
// 응답 메시지 (성공/실패를 문자열로 표현)
String msg;
/**
* Enum 생성자
* - 각 Enum 상수에 대응하는 code와 msg를 초기화
*/
CommonResponse(int code, String msg) {
this.code = code;
this.msg = msg;
}
/**
* 응답 코드 반환
* @return code (예: 0, -1)
*/
public int getCode() {
return code;
}
/**
* 응답 메시지 반환
* @return msg (예: "Success", "Fail")
*/
public String getMsg() {
return msg;
}
}
(5) SignServiceImpl (SignService 인터페이스를 구현)
package com.example.demo.service;
import java.util.Collections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import com.example.demo.config.security.JwtTokenProvider;
import com.example.demo.config.security.common.CommonResponse;
import com.example.demo.jpa.data.dto.SignInResultDto;
import com.example.demo.jpa.data.dto.SignUpResultDto;
import com.example.demo.jpa.data.entity.User;
import com.example.demo.jpa.data.repository.UserRepository;
/**
* 회원가입과 로그인 기능을 실제로 구현한 서비스 클래스
*/
@Service // Spring의 Service 컴포넌트로 등록 → 비즈니스 로직 담당
public class SignServiceImpl implements SignService {
// Logger 인스턴스 생성 → 서비스 동작 과정 로깅
private final Logger LOGGER = LoggerFactory.getLogger(SignServiceImpl.class);
// 의존성 주입 받을 Repository, TokenProvider, PasswordEncoder
public UserRepository userRepository;
public JwtTokenProvider jwtTokenProvider;
public PasswordEncoder passwordEncoder;
// 생성자 주입 → Spring이 자동으로 Bean을 주입해줌
@Autowired
public SignServiceImpl(UserRepository userRepository, JwtTokenProvider jwtTokenProvider, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository; // DB 접근용 Repository
this.jwtTokenProvider = jwtTokenProvider; // JWT 토큰 생성/검증용 Provider
this.passwordEncoder = passwordEncoder; // 비밀번호 암호화/검증용 Encoder
}
@Override
public SignUpResultDto signUp(String id, String password, String name, String role) {
LOGGER.info("[getSignUpResult] 회원 가입 정보 전달"); // 로그 기록
User user;
// role이 admin이면 ROLE_ADMIN 부여, 아니면 ROLE_USER 부여
if (role.equalsIgnoreCase("admin")) {
user = User.builder()
.uid(id) // 사용자 ID
.name(name) // 사용자 이름
.password(passwordEncoder.encode(password)) // 비밀번호 암호화 후 저장
.roles(Collections.singletonList("ROLE_ADMIN")) // 관리자 권한 부여
.build();
} else {
user = User.builder()
.uid(id)
.name(name)
.password(passwordEncoder.encode(password)) // 비밀번호 암호화 후 저장
.roles(Collections.singletonList("ROLE_USER")) // 일반 사용자 권한 부여
.build();
}
// DB에 사용자 저장
User savedUser = userRepository.save(user);
SignUpResultDto signUpResultDto = new SignUpResultDto();
LOGGER.info("[getSignUpResult] userEntity 값이 들어왔는지 확인 후 결과값 주입");
// 저장된 사용자 이름이 비어있지 않으면 성공 처리
if(!savedUser.getName().isEmpty()) {
LOGGER.info("[getSignUpResult] 정상 처리 완료");
setSuccessResult(signUpResultDto); // 성공 응답 세팅
} else {
LOGGER.info("[getSignUpResult] 실패 처리 완료");
setFailResult(signUpResultDto); // 실패 응답 세팅
}
return signUpResultDto; // 결과 반환
}
@Override
public SignInResultDto signIn(String id, String password) throws RuntimeException {
LOGGER.info("[getSignInResult] signDataHandler로 회원 정보 요청");
User user = userRepository.getByUid(id); // ID로 사용자 조회
LOGGER.info("[getSignInResult] Id: {}", id);
LOGGER.info("[getSignInResult] 패스워드 비교 수행");
// 입력한 비밀번호와 DB에 저장된 암호화된 비밀번호 비교
if(!passwordEncoder.matches(password, user.getPassword())) {
throw new RuntimeException(); // 불일치 시 예외 발생
}
LOGGER.info("[getSignInResult] 패스워드 일치");
LOGGER.info("[getSignInResult] SignInResultDto 객체 생성");
// 로그인 성공 시 JWT 토큰 생성 후 DTO에 담음
SignInResultDto signInResultDto = SignInResultDto.builder()
.token(jwtTokenProvider.createToken(String.valueOf(user.getUid()), user.getRoles()))
.build();
LOGGER.info("[getSignInResult] SignInResultDto 객체에 값 주입");
setSuccessResult(signInResultDto); // 성공 응답 세팅
return signInResultDto; // 결과 반환
}
// 성공 응답 세팅 메서드
private void setSuccessResult(SignUpResultDto result) {
result.setSuccess(true); // 성공 여부 true
result.setCode(CommonResponse.SUCCESS.getCode()); // 공통 응답 코드 SUCCESS
result.setMsg(CommonResponse.SUCCESS.getMsg()); // 공통 응답 메시지 SUCCESS
}
// 실패 응답 세팅 메서드
private void setFailResult(SignUpResultDto result) {
result.setSuccess(false); // 성공 여부 false
result.setCode(CommonResponse.FAIL.getCode()); // 공통 응답 코드 FAIL
result.setMsg(CommonResponse.FAIL.getMsg()); // 공통 응답 메시지 FAIL
}
}
(6) SignController
- required=true는 선택 사항으로,
이미 @PathVariable이나 @RequestParam에서 필수로 지정하면 자동 반영되므로 생략 가능
package com.example.demo.controller;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.example.demo.jpa.data.dto.SignInResultDto;
import com.example.demo.jpa.data.dto.SignUpResultDto;
import com.example.demo.service.SignService;
import io.swagger.v3.oas.annotations.Parameter;
/**
* 회원가입 및 로그인 관련 API를 제공하는 REST 컨트롤러
*
* 주요 역할:
* - /sign-api 경로 하위에서 회원가입(sign-up), 로그인(sign-in) 요청 처리
* - SignService를 호출하여 실제 비즈니스 로직 실행
* - 예외 발생 시 @ExceptionHandler로 처리하여 JSON 응답 반환
*
* 특징:
* - @RestController: REST API 응답(JSON)을 반환하는 컨트롤러
* - @RequestMapping("/sign-api"): 모든 엔드포인트가 /sign-api 하위에 위치
*/
@RestController
@RequestMapping("/sign-api")
public class SignController {
// 로깅 객체 생성 → 요청/응답 과정 기록
private final Logger LOGGER = LoggerFactory.getLogger(SignController.class);
// 회원가입/로그인 서비스 의존성
private final SignService signService;
// 생성자 주입 → Spring이 SignService 구현체를 자동으로 주입
@Autowired
public SignController(SignService signService) {
this.signService = signService;
}
/**
* 로그인 API
* - POST /sign-api/sign-in
* - id, password를 받아 로그인 처리
* - 성공 시 JWT 토큰을 포함한 SignInResultDto 반환
*/
@PostMapping(value = "sign-in")
public SignInResultDto signIn(
// Swagger 문서화를 위한 @Parameter 사용
@Parameter(required=true) @RequestParam String id,
@Parameter(required=true) @RequestParam String password)
throws RuntimeException {
LOGGER.info("[signIn] 로그인을 시도하고 있습니다. id: {}, pw: ****", id);
SignInResultDto signInResultDto = signService.signIn(id, password);
// 로그인 성공 시 로그 기록
if (signInResultDto.getCode() == 0) {
LOGGER.info("[signIn] 로그인 성공. id: {}, token: {}", id, signInResultDto.getToken());
}
return signInResultDto;
}
/**
* 회원가입 API
* - POST /sign-api/sign-up
* - id, password, name, role을 받아 회원가입 처리
* - 성공/실패 여부를 SignUpResultDto로 반환
*/
@PostMapping(value = "/sign-up")
public SignUpResultDto signUp(
@Parameter(required=true) @RequestParam String id,
@Parameter(required=true) @RequestParam String password,
@Parameter(required=true) @RequestParam String name,
@Parameter(required=true) @RequestParam String role) {
LOGGER.info("[signUp] 회원가입 수행. id: {}, password: ****, name: {}, role: {}", id, name, role);
SignUpResultDto signUpResultDto = signService.signUp(id, password, name, role);
LOGGER.info("[signUp] 회원가입 완료. id: {}", id);
return signUpResultDto;
}
/**
* 예외 발생 테스트 API
* - GET /sign-api/exception
* - 강제로 RuntimeException 발생시켜 예외 처리 흐름 확인
*/
@GetMapping(value="/exception")
public void exceptionText() throws RuntimeException {
throw new RuntimeException("접근 권한이 없습니다.");
}
/**
* RuntimeException 처리 핸들러
* - 컨트롤러 내에서 발생한 RuntimeException을 잡아 JSON 응답 반환
* - 상태 코드: 400 Bad Request
* - 응답 본문: 에러 타입, 코드, 메시지 포함
*/
@ExceptionHandler(value = RuntimeException.class)
public ResponseEntity<Map<String, String>> ExceptionHandler(RuntimeException e) {
HttpHeaders responseHeaders = new HttpHeaders();
HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
LOGGER.error("ExceptionHandler 호출, {}, {}", e.getCause(), e.getMessage());
// 에러 응답 데이터 구성
Map<String, String> map = new HashMap<>();
map.put("error type", httpStatus.getReasonPhrase()); // "Bad Request"
map.put("code", "400");
map.put("message", "에러 발생");
// ResponseEntity로 JSON 응답 반환
return new ResponseEntity<>(map, responseHeaders, httpStatus);
}
}
*** 다음 편에서 계속됩니다 ***
반응형
'스프링(Spring), 스프링부트(SpringBoot) > 스프링부트(SpringBoot) 기초' 카테고리의 다른 글
| 스프링부트 핵심 가이드(장정우 지음) - 스프링 시큐리티(Spring Security)와 JWT 3편 [책의 마지막 부분] (1) | 2026.04.25 |
|---|---|
| 스프링부트 핵심 가이드(장정우 지음) - 스프링 시큐리티(Spring Security)와 JWT 1편 (1) | 2026.04.10 |
| 스프링부트 핵심 가이드(장정우 지음) - 스프링 시큐리티(Spring Security) 개요 (0) | 2026.03.26 |
| 스프링부트 핵심 가이드(장정우 지음) - 서버 간 통신 2: WebClient (0) | 2026.03.18 |
| 스프링부트 핵심 가이드(장정우 지음) - 서버 간 통신 1: RestTemplate (1) | 2026.03.03 |