JWT 를 이용한 인증 2

이전 게시글이 너무 길어져서 아래에서 이어서 작성한다.

최근 많이 쓰이는 JWT 를 이용해 DB 조회 없이 인증 로직을 만들어볼것이다.

인증 과정은 아래와 같다.

 

  1. 로그인 성공 시 JWT 를 만들어서 응답으로 JWT 를 발급
  2. 인증이 필요한 페이지에 요청 시 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());
}

 

테스트는 성공하고 의도한대로 동작함을 알 수 있다.

+ Recent posts