8단계를 하려는데 이젠 로그인도 해서 인증도 해야하네....
- 8단계 : 로그인
🔻 설명
- JWT를 활용해 로그인 기능을 구현합니다.
- 필터를 활용해 인증 처리를 할 수 있습니다.
🔻 조건
- 이메일과 비밀번호를 활용해 로그인 기능을 구현합니다.
- 로그인 성공 시 JWT 발급 후 반환합니다.
- 모든 요청에서 토큰을 활용하여 인증 처리를 합니다.
- 토큰은 Header에 추가합니다.
- 회원가입과 로그인은 인증 처리에서 제외합니다.
⚠️ 예외 처리
- 로그인 시 이메일과 비밀번호가 일치하지 않을 경우 401을 반환합니다.
- 토큰이 없는 경우 400을 반환합니다.
- 유효 기간이 만료된 토큰의 경우 401을 반환합니다.
먼저 JWT를 검증하고 인증할 AuthFilter.java와 요청과 응답 사이의 로그를 관리할 LoggingFilter.java를 생성한다.
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
String url = httpServletRequest.getRequestURI();
String tokenValue = jwtUtil.getTokenFromRequest(httpServletRequest);
log.info("Request URL: {}", url);
log.info("Extracted Token Value: {}", tokenValue);
if (StringUtils.hasText(url) &&
(url.startsWith("/api/auth/signup") || url.startsWith("/api/auth/login"))
) {
log.info("인증 처리를 하지 않는 URL : " + url);
chain.doFilter(request, response); // 다음 Filter로 이동
} else {
if (StringUtils.hasText(tokenValue)) {
// JWT 토큰 substring
String token = jwtUtil.substringToken(tokenValue);
// 토큰 검증
if (!jwtUtil.validateToken(token)) {
httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
httpServletResponse.getWriter().write("The token is either invalid or expired");
return;
}
// 토큰에서 사용자 정보 가져오기
Claims info = jwtUtil.getUserInfoFromToken(token);
User user = userRepository.findByUsername(info.getSubject()).orElseThrow(() ->
new NullPointerException("Not Found User")
);
request.setAttribute("user", user);
chain.doFilter(request, response); // 다음 Filter로 이동
} else {
httpServletResponse.setStatus(HttpStatus.BAD_REQUEST.value());
httpServletResponse.getWriter().write("Not Found Token");
return;
}
}
}
@Slf4j(topic = "LoggingFilter")
@Component
@Order(1)
public class LoggingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 전처리
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String url = httpServletRequest.getRequestURI();
log.info(url);
chain.doFilter(request, response); // 다음 Filter 로 이동
// 후처리
log.info("비즈니스 로직 완료");
}
}
이번에는 로그인 할 때 사용할 dto를 생성
@Getter
public class LoginRequestDto {
private String email;
private String password;
}
@Getter
@Setter
@NoArgsConstructor
public class LoginResponseDto {
private String msg;
private int statusCode;
public LoginResponseDto(String msg, int statusCode) {
this.msg = msg;
this.statusCode = statusCode;
}
}
회원가입하고 로그인할 AuthController.java를 생성한다.
@RestController
@RequestMapping("/api/auth")
public class AuthController {
private final UserService userService;
private final JwtUtil jwtUtil;
public AuthController(UserService userService, JwtUtil jwtUtil) {
this.userService = userService;
this.jwtUtil = jwtUtil;
}
@PostMapping("/signup")
public ResponseEntity<?> signup(@RequestBody UserRequestDto userRequestDto) {
try {
UserResponseDto userResponseDto = userService.createUser(userRequestDto);
return ResponseEntity.status(HttpStatus.CREATED).body(userResponseDto);
} catch (Exception e) {
// 예외 로그 기록
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("회원가입 실패: " + e.getMessage());
}
}
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequestDto loginRequestDto, HttpServletResponse response) {
try {
User user = userService.authenticate(loginRequestDto.getEmail(), loginRequestDto.getPassword());
String token = jwtUtil.createToken(user.getUsername());
// JWT를 Header에 추가
response.addHeader(HttpHeaders.AUTHORIZATION, token);
// 응답 본문에 메시지와 상태 코드 반환
return ResponseEntity.ok(new LoginResponseDto("로그인 성공", 200));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid email or password");
}
}
}
이러면 잘 되는데 UserService에서도 유저를 생성할 수 있어서 한 번 고민을 해봐야 될 것 같다.