MSA-4. JWT를 이용한 로그인, 로그아웃 구현
로그인 기능 구현 - JWT 토큰
- CustomAuthenticationFilter에서 설정한 URL 로 사용자가 요청을 하면 해당 필터가 요청을 가로챈다.
- 1의 단계에서 인증되지 않은 CustomAuthenticationToken을 생성하고, AuthenticationManager에게 인증처리를 요청한다. 여기서 Token에 사용자가 건낸 아이디와 패스워드를 보관.
- AuthenticationManger는 AuthenticationProvider에게 인증처리를 건네고, CustomAuthenticationProvider가 동작한다.
- CustomAuthenticationProvider에서 CustomUserDetailsService로 DB 에 있는 member 정보를 가져와 AuthenticationToken에 저장한 사용자 정보가 일치하는지 확인한다.
- 일치 시 SuccessHandler
- 실패 시 FailedHandler
- 에러가 발생 시 FailedHandler가 실행된다.
- 인증 성공 후 기본 로직에 의해 인가 확인을 진행한다.
- 인가 실패시 경우에 따라 해당 Handler 를 실행한다.
- 권한이 없을 시 CustomLoginAuthenticationEntrypoint 실행
- 권한 거부 시 CustomAccessDeniedHander 실행
- 인증 성공시 거치지 않음.
정상적으로 로그인 한 경우
회원이 아닌 경우 비밀번호나 이메일이 틀린 경우
Trouble Shooting
Being the Detective in a crime movie where you are also the murderer
Case 1 - 안 돼요 / 아 됐다가? / 아뇨 그냥 안 돼요
로그인 정도야 뭐… ㅋㅋ 그냥 만들면 되는 거 아닌가? ㅎ 라는 아주 안일한 생각으로 가볍게 구글링 후 시도했다가 JWT: 응 아니야~ 하고 바로 털렸다. 하 쒸… 이게 아닌데… 구글링을 더 해서 이 악물고 고쳐 봤지만 여전히 실패. 계속 403이 떴다.
이쯤이면 그냥 옛다~ 하고 200 던져 줄 순 없는 건가?
롤백을 대체 몇 번이나 했는지… 계속 삽질하다가 그냥 다시 하자는 마음가짐으로 롤백하고 코드를 다시 수정했다.
case 2 - 6.1 버전부터 사용이 종료된 메소드
이전 글들을 참고하여 코드를 작성하다 보니, 사용이 종료된 메서드를 대체하고 문법을 다시 정리하는데 시간이 오래 걸렸다.
- Spring Security 6.1 이후부터는 .csrf().disable()와 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) 메서드 호출 방식이 변경됨. 기존의 메서드 체이닝 방식 대신에 Fluent API로 변경되었으며, http 메서드에 대한 설정이 좀 더 명시적으로 되어야 함.
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(jwtTokenFilter(), UsernamePasswordAuthenticationFilter.class)
이외에도 많았는데… 어차피 IDE에서 다 알려 주니 다른 것들은 그냥 IDE 내에서 수정했다.
Case 3 - JWT 파싱 오류
JWT를 파싱할 때는 다음과 같은 방식으로 수정한다.
- JWT 파싱: JWT를 파싱하기 위해서는 JWT 토큰을 먼저 추출해야 하며, 보통 HTTP 요청에서 JWT 토큰은 헤더나 요청 바디에 포함되어 있다.
- 토큰 추출: HTTP 요청에서 JWT 토큰을 추출한다. 보통 HTTP 요청의 헤더에서 JWT를 추출하는 방식이 많이 사용한다.
- 토큰 검증: 추출한 JWT 토큰을 검증하고, 유효성을 확인한다. 이 과정에서 서명 키와 함께 JWT 토큰을 파싱하여 필요한 클레임을 추출한다.
// as-is
public String getTokenFromRequest(HttpServletRequest req) {
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(req).getBody();
}
// to-be
public String getTokenFromRequest(HttpServletRequest req) {
String bearerToken = req.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7); // "Bearer " 이후의 토큰 값만 추출
}
return null;
}
- getTokenFromRequest() 메서드: HTTP 요청에서 JWT 토큰을 추출하는 메서드이며 보통 Authorization 헤더에서 “Bearer “ 토큰을 추출하여 반환한다.
- getClaimsFromToken() 메서드: JWT 토큰에서 클레임을 추출한다.
- Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(token).getBody()를 사용하여 토큰을 파싱하고, 클레임을 추출한다.
Case4 - 의존성 주입 실수
아직 Springboot가 익숙하지 않아 Bean이 제대로 주입되지 않거나 어노테이션 실수를 자주 했다. 진짜 트러블 슈팅이라고 적기에도 쪽팔린데… 단순 문법 실수라 공부를 계속 하는 수밖에 없다. 오늘의 트러블슈팅 과정을 되짚어 보자면
- 흐름을 명확히 파악하고 코드를 작성하자. 무턱대고 작성하는 게 더 오래 걸림.
- 자바 문법이 아직도 어색하다. ;; (그렇다고 C++을 능수능란하게 적는 것도 아님.) 스프링부트 공부를 좀 더 해야할 필요를 느꼈다.
Comments