JWT 를 이용한 인증 2
이전 게시글이 너무 길어져서 아래에서 이어서 작성한다.
최근 많이 쓰이는 JWT 를 이용해 DB 조회 없이 인증 로직을 만들어볼것이다.
인증 과정은 아래와 같다.
- 로그인 성공 시 JWT 를 만들어서 응답으로 JWT 를 발급
- 인증이 필요한 페이지에 요청 시 Authorization 헤더에 담긴 JWT 를 확인하여 인증
2. 인증이 필요한 페이지에 요청 시 Authorization 헤더에 담긴 JWT 를 확인하여 인증
AuthController
이전에 만들었던 인증이 필요한 애노테이션 @Login 이 달린 컨트롤러를 재사용한다.
이전에 만들어뒀던 UserSession 객체에 사용자의 이름을 담아온다.
@GetMapping("/auth")
public String access(@Login UserSession session) {
return session.getName() + " 님 안녕하세요";
}
AuthResolver
AppConfig 에서 secretKey 값을 받아서 JWT를 검증한다.
인증 성공 시 JWT의 subject 에 담겨있던 사용자의 이름을 UserSession 객체에 담아 컨트롤러에 반환한다.
@Slf4j
@RequiredArgsConstructor
public class AuthResolver implements HandlerMethodArgumentResolver {
private final AppConfig appConfig;
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(Login.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String jws = webRequest.getHeader("Authorization");
if (jws == null || jws.isBlank()) {
log.error("토큰이 없어요");
throw new UnauthorizedException();
}
SecretKey key = Keys.hmacShaKeyFor(appConfig.getJwtKey());
try {
Jws<Claims> claimsJws = Jwts.parser()
.verifyWith(key)
.build()
.parseSignedClaims(jws);
return new UserSession(claimsJws.getPayload().getSubject());
} catch (JwtException e) {
log.info("올바르지 않은 JWT 토큰 정보");
throw new UnauthorizedException();
}
}
}
WebMvcConfig
AuthResolver 의 생성자가 바꼈으므로 해당 부분을 수정한다.
@Configuration
@RequiredArgsConstructor
public class WebMvcConfig implements WebMvcConfigurer {
private final AppConfig appConfig;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new AuthResolver(appConfig));
}
}
테스트
AuthControllerTest
Mockito 를 이용하여 더 좋은 테스트를 작성할수도 있을것같지만 다음번에 해보도록 하겠다.
@DisplayName("검증된 세션 값으로 권한이 필요한 페이지에 요청시 접속에 성공한다.")
@Test
void access() throws Exception {
// given
User user = userRepository.findByEmailAndPassword("hyukkind@naver.com", "1234")
.orElseThrow(() -> new InvalidSigninInformationException());
// expected
mockMvc.perform(
get("/auth")
.header("Authorization", "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJtaWRjb24ifQ.u_88_mWPpVXBTF1l4LcbYnhOJMS2Iksz25GFLmhgiKo")
.contentType(APPLICATION_JSON)
)
.andDo(print())
.andExpect(status().isOk());
}
@DisplayName("검증되지 않은 세션값으로 권한이 필요한 페이지에 요청시 실패한다.")
@Test
void access2() throws Exception {
// given
User user = userRepository.findByEmailAndPassword("hyukkind@naver.com", "1234")
.orElseThrow(() -> new InvalidSigninInformationException());
// expected
mockMvc.perform(
get("/auth")
.header("Authorization", "1")
.contentType(APPLICATION_JSON)
)
.andDo(print())
.andExpect(status().isUnauthorized());
}
테스트는 성공하고 의도한대로 동작함을 알 수 있다.
'API 만들어 보기 > 게시판 API' 카테고리의 다른 글
[API 인증] 회원가입과 비밀번호 암호화 (0) | 2023.10.09 |
---|---|
[API 인증] JWT 를 이용한 인증 1 (0) | 2023.10.07 |
[API 인증] 쿠키를 통한 인증 및 검증 (0) | 2023.10.03 |
[API 인증] DB를 통한 토큰 발급 및 검증 2 (0) | 2023.10.02 |
[API 인증] DB를 통한 토큰 발급 및 검증 1 (0) | 2023.10.01 |