JWT 란

JWT(Json Web Token) 는 Json 형식을 이용하여 사용자에 대한 속성을 저장하는 Web Token이다. 

JWT 에 관한 정보는 여기에 정리해두었다.

 

JWT 를 이용한 인증 1

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

기존의 인증 및 검증 과정은 DB 가 필요했지만 JWT 는 DB에서 확인하는 과정이 필요 없다는 장점이 있다.

인증 과정은 아래와 같다.

 

  1. 로그인 성공 시 JWT 를 만들어서 응답으로 JWT 를 발급
  2. 인증이 필요한 페이지에 요청 시 Authorization 헤더에 담긴 JWT 를 확인하여 인증

 


1. 로그인 성공 시 JWT 를 만들어서 응답으로 JWT 를 발급

이 글에서는 JWT를 만들고 응답에 넣어서 보내는것 까지만 설명한다.

인증을 처리하는 과정이 궁금하다면 다음 글을 확인하자.

AuthController

SecretKey 를 Application.yml 에 설정해두고 AppConfig 에서 이 설정 정보를 읽어온다.

이 AppConfig 에서 SecretKey를 꺼내와서 토큰 발급 및 검증에 사용한다.

application.yml 에서 프로퍼티 정보를 가져오는건 이 글에 정리해두었다.

로그인 성공 시 사용자의 이름과 발급 시간 정보가 담긴 JWT 토큰을 응답값에 담아 반환한다.

Claim 에 토큰 수명을 설정할수도 있다.

@Slf4j
@RestController
@RequiredArgsConstructor
public class AuthController {

    private final AuthService authService;
    private final AppConfig appConfig;

    @PostMapping("/auth/login")
    public SessionResponse login(@RequestBody LoginRequest request) {
        String name = authService.signin(request);
        SecretKey secretKey = Keys.hmacShaKeyFor(appConfig.getJwtKey());
        // 토큰을 응답
        return new SessionResponse(Jwts.builder()
            .subject(name)
            .issuedAt(new Date())
            .signWith(secretKey)
            .compact());
    }
}

 

application.yml

midcon:
  jwt-key: "pjx7jVXbdaeOmw0ZO1SotIHLVApe8FZ+LmGCuMKa8T8="

 

AppConfig

AppConfig 에서 jwtKey 정보를 가져올때 굳이 인코딩 된 jwtKey 를 가져올 필요가 없으므로 AppConfig 에서 디코딩 한다.

@RequiredArgsConstructor
@ConstructorBinding
@ConfigurationProperties(prefix = "midcon")
public class AppConfig {

    private final String jwtKey;

    public byte[] getJwtKey() {
        return Decoders.BASE64.decode(jwtKey);
    }
}

 

AuthService

로그인 성공 시 유저 이름을 반환하도록 수정했다.

@Service
@RequiredArgsConstructor
public class AuthService {

    private final UserRepository userRepository;

    @Transactional
    public String signin(LoginRequest request) {
        User user = userRepository.findByEmailAndPassword(request.getEmail(), request.getPassword())
            .orElseThrow(() -> new InvalidSigninInformationException());
        return user.getName();
    }
}

 


테스트

AuthServiceTest

서비스 로직에 변화가 생겨서 메서드가 반환한 유저 이름을 확인하는 테스트로 수정한다.

@DisplayName("로그인 요청 시 DB 정보와 일치하면 accessToken 을 발급한다.")
@Test
void signin() throws JsonProcessingException {
    // given
    LoginRequest request = LoginRequest.builder()
        .email("hyukkind@naver.com")
        .password("1234")
        .build();

    // when
    String name = authService.signin(request);
    User user = userRepository.findByEmailAndPassword(request.getEmail(), request.getPassword())
        .orElseThrow(() -> new InvalidSigninInformationException());

    // then
    assertEquals(user.getName(), name);
}

 

테스트는 통과하고 의도했던대로 동작함을 알 수 있다.

 


AuthControllerTest

로그인의 성공, 실패 테스트를 실행하고 body 에 JWT 가 응답으로 내려왔는지 확인하는 테스트를 작성한다.

Mockito 를 이용하여 JWT 토큰 값을 임의로 정해서 확인하는 테스트도 만들 수 있을것같지만 추후에 해보도록 하자.

@DisplayName("로그인 성공")
@Test
void login() throws Exception {
    // given
    LoginRequest request = LoginRequest.builder()
        .email("hyukkind@naver.com")
        .password("1234")
        .build();

    String json = objectMapper.writeValueAsString(request);

    // expected
    mockMvc.perform(
        post("/auth/login")
            .contentType(APPLICATION_JSON)
            .content(json)
        )
        .andDo(print())
        .andExpect(status().isOk());
}

@DisplayName("로그인 실패")
@Test
void login2() throws Exception {
    // given
    LoginRequest request = LoginRequest.builder()
        .email("hyukkind@naver.com")
        .password("1111")
        .build();

    String json = objectMapper.writeValueAsString(request);

    // expected
    mockMvc.perform(
            post("/auth/login")
                .contentType(APPLICATION_JSON)
                .content(json)
        )
        .andDo(print())
        .andExpect(status().isBadRequest());
}

 

테스트는 성공하고 원하는대로 JWT 또한 제대로 응답에 담겨서 내려오는걸 알 수 있다.

글이 길어져서 다음 글에서 이어서 작성한다.

+ Recent posts